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

OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{tok

Error message

OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})

What it means

Raised by Process.open_process_token (lib/puppet/util/windows/process.rb:95) when OpenProcessToken returns FALSE for the given process handle and requested access mask. ERROR_ACCESS_DENIED (5) dominates: the token does not grant everything in the mask. Notably Puppet itself calls this with TOKEN_ALL_ACCESS (0xF01FF) in process_privilege_symlink?, which can be denied for a filtered (UAC-de-elevated) token, while TOKEN_QUERY (0x0008) succeeds.

Source

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

      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
          raise Puppet::Util::Windows::Error, "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})"
        end

        yield token_handle = token_handle_ptr.read_handle
      end

      token_handle
    ensure
      FFI::WIN32.CloseHandle(token_handle) if token_handle
    end

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

  # Execute a block with the current process token
  def with_process_token(access, &block)
    handle = get_current_process

View on GitHub (pinned to e227c27540)

Solutions

  1. Downgrade the access request: TOKEN_QUERY for reads, TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES when enabling privileges.
  2. For the current process use Process.get_current_process (pseudo-handle) instead of opening by pid.
  3. Run elevated (or as an agent under SYSTEM) when inspecting other processes' tokens.
  4. Rescue and read e.code — 5 (access denied) vs 6 (invalid handle from a lifecycle bug).

Example fix

# before — TOKEN_ALL_ACCESS on a filtered/UAC token can be denied
Process.open_process_token(handle, Process::TOKEN_ALL_ACCESS) { |t| use(t) }

# after — ask only for what is needed, on the current process
Process.open_process_token(Process.get_current_process,
                           Process::TOKEN_QUERY | Puppet::Util::Windows::Security::TOKEN_ADJUST_PRIVILEGES) { |t| use(t) }
Defensive patterns

Strategy: try-catch

Try / catch

begin
  Process.open_process_token(handle, desired_access) { |t| yield t }
rescue Puppet::Util::Windows::Error => e
  if e.code == 5 && (desired_access & ~Process::TOKEN_QUERY) != 0
    Process.open_process_token(handle, Process::TOKEN_QUERY) { |t| yield t } # retry narrower
  else
    raise
  end
end

Prevention

When it happens

Trigger: Requesting TOKEN_ALL_ACCESS or TOKEN_ADJUST_PRIVILEGES on a process the caller does not fully own (other session, elevated process seen from a non-elevated agent, filtered admin token); passing a NULL or already-closed process handle; opening a protected process's token.

Common situations: Non-elevated Ruby code adjusting privileges or querying token state; agents inspecting service processes; handles saved beyond the open_process_token block that guaranteed their lifetime.

Related errors


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