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
251
252
253
254
255
256
257
258
# File 'lib/puppet/util/windows/process.rb', line 236

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 #{env_str} which contains invalid bytes")
        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_system_default_ui_languageObject



280
281
282
# File 'lib/puppet/util/windows/process.rb', line 280

def get_system_default_ui_language
  GetSystemDefaultUILanguage()
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



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/puppet/util/windows/process.rb', line 261

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)


290
291
292
# File 'lib/puppet/util/windows/process.rb', line 290

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