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

OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process

Error message

OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process_id})

What it means

Raised by Process.open_process (lib/puppet/util/windows/process.rb:76) when the Win32 OpenProcess call returns a NULL handle; the message records the requested access mask (octal), the inherit flag and the pid. Typical codes: ERROR_ACCESS_DENIED (5) — the caller asked for more rights than it holds over the target — and ERROR_INVALID_PARAMETER (87) — the pid does not exist because the process already exited or was never valid.

Source

Thrown at lib/puppet/util/windows/process.rb:76

    end

    exit_status
  end
  module_function :wait_process

  def get_current_process
    # this pseudo-handle does not require closing per MSDN docs
    GetCurrentProcess()
  end
  module_function :get_current_process

  def open_process(desired_access, inherit_handle, process_id, &block)
    phandle = nil
    inherit = inherit_handle ? FFI::WIN32_TRUE : FFI::WIN32_FALSE
    begin
      phandle = OpenProcess(desired_access, inherit, process_id)
      if phandle == FFI::Pointer::NULL_HANDLE
        raise Puppet::Util::Windows::Error, "OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process_id})"
      end

      yield phandle
    ensure
      FFI::WIN32.CloseHandle(phandle) if phandle
    end

    # phandle has had CloseHandle called against it, so nothing to return
    nil
  end
  module_function :open_process

  def open_process_token(handle, desired_access, &block)
    token_handle = nil
    begin
      FFI::MemoryPointer.new(:handle, 1) do |token_handle_ptr|
        result = OpenProcessToken(handle, desired_access, token_handle_ptr)
        if result == FFI::WIN32_FALSE

View on GitHub (pinned to e227c27540)

Solutions

  1. Treat it as a race first: when the pid is gone (e.code 87) skip or retry instead of failing.
  2. Request the least access that works — PROCESS_QUERY_LIMITED_INFORMATION (0x1000) succeeds where PROCESS_QUERY_INFORMATION is denied for same-user processes.
  3. Run elevated or under an account that can enable SeDebugPrivilege when inspecting other users' processes (Puppet wraps this in Security.with_privilege).
  4. Rescue Puppet::Util::Windows::Error and branch on e.code (5 vs 87) to distinguish permissions from dead pids.

Example fix

# before
Process.open_process(Process::PROCESS_QUERY_INFORMATION, false, pid) { |h| use(h) }

# after — accept a vanished process instead of raising
begin
  Process.open_process(Process::PROCESS_QUERY_INFORMATION, false, pid) { |h| use(h) }
rescue Puppet::Util::Windows::Error => e
  raise unless e.code == 87 # ERROR_INVALID_PARAMETER — process gone
  nil
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  Process.open_process(access, false, pid) { |h| yield h }
rescue Puppet::Util::Windows::Error => e
  return nil if e.code == 87 # pid gone — expected when racing process exit
  return nil if e.code == 5  # access denied — skip protected/foreign processes
  raise
end

Prevention

When it happens

Trigger: Requesting PROCESS_QUERY_INFORMATION (as get_process_image_name_by_pid does) on a process owned by another session/user without SE_DEBUG privilege; the target pid exiting between enumeration and open; opening a protected-process-light (PPL) binary; passing pid 0 or an invalid value.

Common situations: Process inventory or monitoring code racing short-lived processes; non-elevated agents inspecting elevated/SYSTEM processes; pid reuse between snapshot and open; security software protecting its processes.

Related errors


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