puppetlabs/puppet · error · Puppet::Util::Windows::Error

DeviceIoControl(#{handle}, #{io_control_code}, #{in_buffer},

Error message

DeviceIoControl(#{handle}, #{io_control_code}, #{in_buffer}, #{in_buffer ? in_buffer.size : ''}, #{out_buffer}, #{out_buffer ? out_buffer.size : ''})

What it means

device_io_control raises Puppet::Util::Windows::Error when the DeviceIoControl Win32 call itself returns FALSE, echoing handle, control code, and buffer pointers/sizes into the message. The real reason is in e.code: for FSCTL_GET_REPARSE_POINT the classic values are ERROR_NOT_A_REPARSE_POINT (4390/0x1126) when the file has no reparse data, plus invalid-handle, access-denied, and insufficient-buffer variants.

Source

Thrown at lib/puppet/util/windows/file.rb:197

  end

  def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil)
    if out_buffer.nil?
      raise Puppet::Util::Windows::Error, _("out_buffer is required")
    end

    FFI::MemoryPointer.new(:dword, 1) do |bytes_returned_ptr|
      result = DeviceIoControl(
        handle,
        io_control_code,
        in_buffer, in_buffer.nil? ? 0 : in_buffer.size,
        out_buffer, out_buffer.size,
        bytes_returned_ptr,
        nil
      )

      if result == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error, "DeviceIoControl(#{handle}, #{io_control_code}, " \
                                            "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " \
                                            "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}"
      end
    end

    out_buffer
  end

  def reparse_point?(file_name)
    attributes = get_attributes(file_name, false)

    return false if attributes == INVALID_FILE_ATTRIBUTES

    (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
  end
  module_function :reparse_point?

  def symlink?(file_name)

View on GitHub (pinned to e227c27540)

Solutions

  1. Gate on e.code: 4390 means 'not a reparse point' (skip, don't fail), 6 stale handle (reopen), 122 grow the buffer
  2. Check reparse_point?(file_name) before opening/issuing FSCTL_GET_REPARSE_POINT
  3. Open handles with FILE_FLAG_OPEN_REPARSE_POINT when inspecting links
  4. Allocate reparse out_buffers >= MAXIMUM_REPARSE_DATA_BUFFER_SIZE (10240)

Example fix

// before
Puppet::Util::Windows::File.device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out)  # plain file -> 4390

// after
if Puppet::Util::Windows::File.reparse_point?(path)
  Puppet::Util::Windows::File.device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out)
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise ArgumentError, 'not a reparse point' unless Puppet::Util::Windows::File.reparse_point?(path)

Try / catch

begin
  Puppet::Util::Windows::File.device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out_buffer)
rescue Puppet::Util::Windows::Error => e
  raise unless e.code == 4390  # ERROR_NOT_A_REPARSE_POINT - benign probe miss
  nil
end

Prevention

When it happens

Trigger: Sending FSCTL_GET_REPARSE_POINT to a handle opened on a plain (non-reparse) file (4390); using a stale or closed handle (6); a handle opened without FILE_FLAG_OPEN_REPARSE_POINT so the filter never sees the request; an out_buffer smaller than the reparse data requires (122, ERROR_INSUFFICIENT_BUFFER).

Common situations: Symlink-inspection code that assumes every file is a reparse point after a racy existence check; handles kept across I/O boundaries and invalidated by filter drivers; buffers sized below the documented 10240-byte minimum for reparse data.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/39020a2c0f817e85. Report an issue: GitHub.