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
WAIT_INTERVAL =
200
CREATE_NO_WINDOW =
0x08000000
PROCESS_QUERY_INFORMATION =
0x0400
MAX_PATH_LENGTH =
32767
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)


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

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



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

def execute(command, arguments, stdin, stdout, stderr)
  create_args = {
    :command_line => command,
    :startup_info => {
      :stdin => stdin,
      :stdout => stdout,
      :stderr => stderr
    },
    :close_handles => false,
  }
  if arguments[:suppress_window]
    create_args[:creation_flags] = CREATE_NO_WINDOW
  end
  if arguments[:cwd]
    create_args[:cwd] = arguments[:cwd]
  end
  Process.create(create_args)
end

.get_current_processObject



61
62
63
64
# File 'lib/puppet/util/windows/process.rb', line 61

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 practical limit of 64K is used as no single environment variable can exceed 32KB


304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/puppet/util/windows/process.rb', line 304

def get_environment_strings
  env_ptr = GetEnvironmentStringsW()

  # pass :invalid => :replace to the Ruby String#encode to use replacement characters
  pairs = env_ptr.read_arbitrary_wide_string_up_to(65534, :double_null, { :invalid => :replace })
    .split(?\x00)
    .reject { |env_str| env_str.nil? || env_str.empty? || env_str[0] == '=' }
    .reject do |env_str|
      # reject any string containing the Unicode replacement character
      if env_str.include?("\uFFFD")
        Puppet.warning(_("Discarding environment variable %{string} which contains invalid bytes") % { string: env_str })
        true
      end
    end
    .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_process_image_name_by_pid(pid) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/puppet/util/windows/process.rb', line 122

def get_process_image_name_by_pid(pid)
  image_name = ""

  open_process(PROCESS_QUERY_INFORMATION, false, pid) do |phandle|

    FFI::MemoryPointer.new(:dword, 1) do |exe_name_length_ptr|
      # Add 1 for the null terminator, and UTF is 2 bytes/char:
      max_path_length = (MAX_PATH_LENGTH + 1) * 2
      exe_name_length_ptr.write_dword(max_path_length)
      FFI::MemoryPointer.new(max_path_length) do |exe_name_ptr|
        use_win32_path_format = 0
        result = QueryFullProcessImageNameW(phandle, use_win32_path_format, exe_name_ptr, exe_name_length_ptr)
        if result == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new(
            "QueryFullProcessImageNameW(phandle, #{use_win32_path_format}, " +
            "exe_name_ptr, #{max_path_length}")
        end
        image_name = exe_name_ptr.read_wide_string(MAX_PATH_LENGTH + 1)
      end
    end
  end

  image_name
end

.get_system_default_ui_languageObject



348
349
350
# File 'lib/puppet/util/windows/process.rb', line 348

def get_system_default_ui_language
  GetSystemDefaultUILanguage()
end

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



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/puppet/util/windows/process.rb', line 169

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



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/puppet/util/windows/process.rb', line 148

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(desired_access, inherit_handle, process_id, &block) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/puppet/util/windows/process.rb', line 67

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

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



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/puppet/util/windows/process.rb', line 87

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



216
217
218
# File 'lib/puppet/util/windows/process.rb', line 216

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



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/puppet/util/windows/process.rb', line 200

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)


223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/puppet/util/windows/process.rb', line 223

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



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/puppet/util/windows/process.rb', line 329

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}") % { name: 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}") % { name: 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)


358
359
360
# File 'lib/puppet/util/windows/process.rb', line 358

def supports_elevated_security?
  windows_major_version >= 6
end

.wait_process(handle) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/puppet/util/windows/process.rb', line 38

def wait_process(handle)
  while WaitForSingleObject(handle, WAIT_INTERVAL) == WAIT_TIMEOUT
    sleep(0)
  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



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/puppet/util/windows/process.rb', line 275

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



111
112
113
114
115
116
117
118
119
# File 'lib/puppet/util/windows/process.rb', line 111

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