Module: Puppet::Util::Windows::Process

Extended by:
FFI::Library, String
Defined in:
lib/puppet/util/windows/process.rb

Defined Under Namespace

Classes: LUID, LUID_AND_ATTRIBUTES, OSVERSIONINFO, TOKEN_ELEVATION, TOKEN_PRIVILEGES

Constant Summary collapse

WAIT_TIMEOUT =
0x102
TOKEN_ALL_ACCESS =
0xF01FF
ERROR_NO_SUCH_PRIVILEGE =
1313
TOKEN_QUERY =
0x0008
ABOVE_NORMAL_PRIORITY_CLASS =
0x0008000
BELOW_NORMAL_PRIORITY_CLASS =
0x0004000
HIGH_PRIORITY_CLASS =
0x0000080
IDLE_PRIORITY_CLASS =
0x0000040
NORMAL_PRIORITY_CLASS =
0x0000020
REALTIME_PRIORITY_CLASS =
0x0000010
TOKEN_INFORMATION_CLASS =
enum(
    :TokenUser, 1,
    :TokenGroups,
    :TokenPrivileges,
    :TokenOwner,
    :TokenPrimaryGroup,
    :TokenDefaultDacl,
    :TokenSource,
    :TokenType,
    :TokenImpersonationLevel,
    :TokenStatistics,
    :TokenRestrictedSids,
    :TokenSessionId,
    :TokenGroupsAndPrivileges,
    :TokenSessionReference,
    :TokenSandBoxInert,
    :TokenAuditPolicy,
    :TokenOrigin,
    :TokenElevationType,
    :TokenLinkedToken,
    :TokenElevation,
    :TokenHasRestrictions,
    :TokenAccessInformation,
    :TokenVirtualizationAllowed,
    :TokenVirtualizationEnabled,
    :TokenIntegrityLevel,
    :TokenUIAccess,
    :TokenMandatoryPolicy,
    :TokenLogonSid,
    :TokenIsAppContainer,
    :TokenCapabilities,
    :TokenAppContainerSid,
    :TokenAppContainerNumber,
    :TokenUserClaimAttributes,
    :TokenDeviceClaimAttributes,
    :TokenRestrictedUserClaimAttributes,
    :TokenRestrictedDeviceClaimAttributes,
    :TokenDeviceGroups,
    :TokenRestrictedDeviceGroups,
    :TokenSecurityAttributes,
    :TokenIsRestricted,
    :MaxTokenInfoClass
)

Class Method Summary collapse

Methods included from String

wide_string

Methods included from FFI::Library

attach_function_private

Class Method Details

.elevated_security?Boolean

Returns whether or not the owner of the current process is running with elevated security privileges.

Only supported on Windows Vista or later.

Returns:

  • (Boolean)


183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/puppet/util/windows/process.rb', line 183

def elevated_security?
  # default / pre-Vista
  elevated = false
  handle = nil

  begin
    handle = get_current_process
    open_process_token(handle, TOKEN_QUERY) do |token_handle|
      get_token_information(token_handle, :TokenElevation) do |token_info|
        token_elevation = parse_token_information_as_token_elevation(token_info)
        # TokenIsElevated member of the TOKEN_ELEVATION struct
        elevated = token_elevation[:TokenIsElevated] != 0
      end
    end

    elevated
  rescue Puppet::Util::Windows::Error => e
    raise e if e.code != ERROR_NO_SUCH_PRIVILEGE
  ensure
    FFI::WIN32.CloseHandle(handle) if handle
  end
end

.execute(command, arguments, stdin, stdout, stderr) ⇒ Object



11
12
13
# File 'lib/puppet/util/windows/process.rb', line 11

def execute(command, arguments, stdin, stdout, stderr)
  Process.create( :command_line => command, :startup_info => {:stdin => stdin, :stdout => stdout, :stderr => stderr}, :close_handles => false )
end

.get_current_processObject



39
40
41
42
# File 'lib/puppet/util/windows/process.rb', line 39

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

.get_environment_stringsObject

Returns a hash of the current environment variables encoded as UTF-8 The memory block returned from GetEnvironmentStringsW is double-null terminated and the vars are paired as below; Var1=Value10 Var2=Value20 VarX=ValueX00 Note - Some env variable names start with ‘=’ and are excluded from the return value Note - The env_ptr MUST be freed using the FreeEnvironmentStringsW function Note - There is no technical limitation to the size of the environment block returned.

However a pracitcal limit of 64K is used as no single environment variable can exceed 32KB


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/puppet/util/windows/process.rb', line 236

def get_environment_strings
  env_ptr = GetEnvironmentStringsW()

  pairs = env_ptr.read_arbitrary_wide_string_up_to(65534, :double_null)
    .split(?\x00)
    .reject { |env_str| env_str.nil? || env_str.empty? || env_str[0] == '=' }
    .map { |env_pair| env_pair.split('=', 2) }
  Hash[ pairs ]
ensure
  if env_ptr && ! env_ptr.null?
    if FreeEnvironmentStringsW(env_ptr) == FFI::WIN32_FALSE
      Puppet.debug "FreeEnvironmentStringsW memory leak"
    end
  end
end

.get_token_information(token_handle, token_information, &block) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/puppet/util/windows/process.rb', line 101

def get_token_information(token_handle, token_information, &block)
  # to determine buffer size
  FFI::MemoryPointer.new(:dword, 1) do |return_length_ptr|
    result = GetTokenInformation(token_handle, token_information, nil, 0, return_length_ptr)
    return_length = return_length_ptr.read_dword

    if return_length <= 0
      raise Puppet::Util::Windows::Error.new(
        "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})")
    end

    # re-call API with properly sized buffer for all results
    FFI::MemoryPointer.new(return_length) do |token_information_buf|
      result = GetTokenInformation(token_handle, token_information,
        token_information_buf, return_length, return_length_ptr)

      if result == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error.new(
          "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " +
            "#{return_length}, #{return_length_ptr})")
      end

      yield token_information_buf
    end
  end

  # GetTokenInformation buffer has been cleaned up by this point, nothing to return
  nil
end

.lookup_privilege_value(name, system_name = '', &block) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/puppet/util/windows/process.rb', line 80

def lookup_privilege_value(name, system_name = '', &block)
  FFI::MemoryPointer.new(LUID.size) do |luid_ptr|
    result = LookupPrivilegeValueW(
      wide_string(system_name),
      wide_string(name.to_s),
      luid_ptr
      )

    if result == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error.new(
        "LookupPrivilegeValue(#{system_name}, #{name}, #{luid_ptr})")
    end

    yield LUID.new(luid_ptr)
  end

  # the underlying MemoryPointer for LUID is cleaned up by this point
  nil
end

.open_process_token(handle, desired_access, &block) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/puppet/util/windows/process.rb', line 45

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.new(
          "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

.parse_token_information_as_token_elevation(token_information_buf) ⇒ Object



148
149
150
# File 'lib/puppet/util/windows/process.rb', line 148

def parse_token_information_as_token_elevation(token_information_buf)
  TOKEN_ELEVATION.new(token_information_buf)
end

.parse_token_information_as_token_privileges(token_information_buf) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/puppet/util/windows/process.rb', line 132

def parse_token_information_as_token_privileges(token_information_buf)
  raw_privileges = TOKEN_PRIVILEGES.new(token_information_buf)
  privileges = { :count => raw_privileges[:PrivilegeCount], :privileges => [] }

  offset = token_information_buf + TOKEN_PRIVILEGES.offset_of(:Privileges)
  privilege_ptr = FFI::Pointer.new(LUID_AND_ATTRIBUTES, offset)

  # extract each instance of LUID_AND_ATTRIBUTES
  0.upto(privileges[:count] - 1) do |i|
    privileges[:privileges] <<  LUID_AND_ATTRIBUTES.new(privilege_ptr[i])
  end

  privileges
end

.process_privilege_symlink?Boolean

Returns:

  • (Boolean)


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/puppet/util/windows/process.rb', line 155

def process_privilege_symlink?
  privilege_symlink = false
  handle = get_current_process
  open_process_token(handle, TOKEN_ALL_ACCESS) do |token_handle|
    lookup_privilege_value('SeCreateSymbolicLinkPrivilege') do |luid|
      get_token_information(token_handle, :TokenPrivileges) do |token_info|
        token_privileges = parse_token_information_as_token_privileges(token_info)
        privilege_symlink = token_privileges[:privileges].any? { |p| p[:Luid].values == luid.values }
      end
    end
  end

  privilege_symlink
rescue Puppet::Util::Windows::Error => e
  if e.code == ERROR_NO_SUCH_PRIVILEGE
    false # pre-Vista
  else
    raise e
  end
end

.set_environment_variable(name, val) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/puppet/util/windows/process.rb', line 253

def set_environment_variable(name, val)
  raise Puppet::Util::Windows::Error('environment variable name must not be nil or empty') if ! name || name.empty?

  FFI::MemoryPointer.from_string_to_wide_string(name) do |name_ptr|
    if (val.nil?)
      if SetEnvironmentVariableW(name_ptr, FFI::MemoryPointer::NULL) == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error.new("Failed to remove environment variable: #{name}")
      end
    else
      FFI::MemoryPointer.from_string_to_wide_string(val) do |val_ptr|
        if SetEnvironmentVariableW(name_ptr, val_ptr) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new("Failed to set environment variable: #{name}")
        end
      end
    end
  end
end

.supports_elevated_security?Boolean

Returns whether or not the OS has the ability to set elevated token information.

Returns true on Windows Vista or later, otherwise false

Returns:

  • (Boolean)


277
278
279
# File 'lib/puppet/util/windows/process.rb', line 277

def supports_elevated_security?
  windows_major_version >= 6
end

.wait_process(handle) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/puppet/util/windows/process.rb', line 16

def wait_process(handle)
  while WaitForSingleObject(handle, 0) == WAIT_TIMEOUT
    sleep(1)
  end

  exit_status = -1
  FFI::MemoryPointer.new(:dword, 1) do |exit_status_ptr|
    if GetExitCodeProcess(handle, exit_status_ptr) == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error.new("Failed to get child process exit code")
    end
    exit_status = exit_status_ptr.read_dword

    # $CHILD_STATUS is not set when calling win32/process Process.create
    # and since it's read-only, we can't set it. But we can execute a
    # a shell that simply returns the desired exit status, which has the
    # desired effect.
    %x{#{ENV['COMSPEC']} /c exit #{exit_status}}
  end

  exit_status
end

.windows_major_versionObject



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/puppet/util/windows/process.rb', line 207

def windows_major_version
  ver = 0

  FFI::MemoryPointer.new(OSVERSIONINFO.size) do |os_version_ptr|
    os_version = OSVERSIONINFO.new(os_version_ptr)
    os_version[:dwOSVersionInfoSize] = OSVERSIONINFO.size

    result = GetVersionExW(os_version_ptr)

    if result == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error.new("GetVersionEx failed")
    end

    ver = os_version[:dwMajorVersion]
  end

  ver
end

.with_process_token(access, &block) ⇒ Object

Execute a block with the current process token



69
70
71
72
73
74
75
76
77
# File 'lib/puppet/util/windows/process.rb', line 69

def with_process_token(access, &block)
  handle = get_current_process
  open_process_token(handle, access) do |token_handle|
    yield token_handle
  end

  # all handles have been closed, so nothing to safely return
  nil
end