{"record":{"id":"21a16bb5171a8823","repo":"puppetlabs/puppet","slug":"openprocess-desired-access-to-s-8-inherit","errorCode":null,"errorMessage":"OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process_id})","messagePattern":"OpenProcess\\(#(.+?), #(.+?), #(.+?)\\)","errorType":"exception","errorClass":"Puppet::Util::Windows::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/windows/process.rb","lineNumber":76,"sourceCode":"    end\n\n    exit_status\n  end\n  module_function :wait_process\n\n  def get_current_process\n    # this pseudo-handle does not require closing per MSDN docs\n    GetCurrentProcess()\n  end\n  module_function :get_current_process\n\n  def open_process(desired_access, inherit_handle, process_id, &block)\n    phandle = nil\n    inherit = inherit_handle ? FFI::WIN32_TRUE : FFI::WIN32_FALSE\n    begin\n      phandle = OpenProcess(desired_access, inherit, process_id)\n      if phandle == FFI::Pointer::NULL_HANDLE\n        raise Puppet::Util::Windows::Error, \"OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process_id})\"\n      end\n\n      yield phandle\n    ensure\n      FFI::WIN32.CloseHandle(phandle) if phandle\n    end\n\n    # phandle has had CloseHandle called against it, so nothing to return\n    nil\n  end\n  module_function :open_process\n\n  def open_process_token(handle, desired_access, &block)\n    token_handle = nil\n    begin\n      FFI::MemoryPointer.new(:handle, 1) do |token_handle_ptr|\n        result = OpenProcessToken(handle, desired_access, token_handle_ptr)\n        if result == FFI::WIN32_FALSE","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/windows/process.rb#L58-L94","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Treat it as a race first: when the pid is gone (e.code 87) skip or retry instead of failing.","Request the least access that works — PROCESS_QUERY_LIMITED_INFORMATION (0x1000) succeeds where PROCESS_QUERY_INFORMATION is denied for same-user processes.","Run elevated or under an account that can enable SeDebugPrivilege when inspecting other users' processes (Puppet wraps this in Security.with_privilege).","Rescue Puppet::Util::Windows::Error and branch on e.code (5 vs 87) to distinguish permissions from dead pids."],"exampleFix":"# before\nProcess.open_process(Process::PROCESS_QUERY_INFORMATION, false, pid) { |h| use(h) }\n\n# after — accept a vanished process instead of raising\nbegin\n  Process.open_process(Process::PROCESS_QUERY_INFORMATION, false, pid) { |h| use(h) }\nrescue Puppet::Util::Windows::Error => e\n  raise unless e.code == 87 # ERROR_INVALID_PARAMETER — process gone\n  nil\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  Process.open_process(access, false, pid) { |h| yield h }\nrescue Puppet::Util::Windows::Error => e\n  return nil if e.code == 87 # pid gone — expected when racing process exit\n  return nil if e.code == 5  # access denied — skip protected/foreign processes\n  raise\nend","preventionTips":["Ask for the narrowest access mask that suffices","Re-check pid liveness immediately before opening and treat misses as skips","Run inventory code as SYSTEM or elevated when crossing user sessions","Never assume a pid from an earlier snapshot still exists"],"tags":["windows","win32-api","process","permissions","race-condition","ffi","puppet"],"backgroundTag":"win32-openprocess-access-denied","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}