@@ -17,6 +17,8 @@ module Registry
1717
1818 ERROR_NO_MORE_ITEMS = 259
1919
20+ WCHAR_SIZE = FFI . type_size ( :wchar )
21+
2022 def root ( name )
2123 Win32 ::Registry . const_get ( name )
2224 rescue NameError
@@ -234,7 +236,7 @@ def read(key, name_ptr, *rtype)
234236
235237 string_length = 0
236238 # buffer is raw bytes, *not* chars - less a NULL terminator
237- string_length = ( byte_length / FFI . type_size ( :wchar ) ) - 1 if byte_length > 0
239+ string_length = ( byte_length / WCHAR_SIZE ) - 1 if byte_length > 0
238240
239241 begin
240242 result = case type
@@ -271,14 +273,47 @@ def query_value_ex(key, name_ptr, &block)
271273 FFI ::Pointer ::NULL , type_ptr ,
272274 FFI ::Pointer ::NULL , length_ptr )
273275
274- FFI ::MemoryPointer . new ( :byte , length_ptr . read_dword ) do |buffer_ptr |
276+ # The call to RegQueryValueExW below is potentially unsafe:
277+ # https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw
278+ #
279+ # "If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type,
280+ # the string may not have been stored with the proper terminating
281+ # null characters. Therefore, even if the function returns
282+ # ERROR_SUCCESS, the application should ensure that the string is
283+ # properly terminated before using it; otherwise, it may overwrite a
284+ # buffer. (Note that REG_MULTI_SZ strings should have two
285+ # terminating null characters.)"
286+ #
287+ # Since we don't know if the values will be properly null terminated,
288+ # extend the buffer to guarantee we can append one or two wide null
289+ # characters, without overwriting any data.
290+ base_bytes_len = length_ptr . read_dword
291+ pad_bytes_len = case type_ptr . read_dword
292+ when Win32 ::Registry ::REG_SZ , Win32 ::Registry ::REG_EXPAND_SZ
293+ WCHAR_SIZE
294+ when Win32 ::Registry ::REG_MULTI_SZ
295+ WCHAR_SIZE * 2
296+ else
297+ 0
298+ end
299+
300+ FFI ::MemoryPointer . new ( :byte , base_bytes_len + pad_bytes_len ) do |buffer_ptr |
275301 result = RegQueryValueExW ( key . hkey , name_ptr ,
276302 FFI ::Pointer ::NULL , type_ptr ,
277303 buffer_ptr , length_ptr )
278304
279- if result != FFI ::ERROR_SUCCESS
305+ # Ensure buffer is null terminated with 1 or 2 wchar nulls, depending on the type
306+ if result == FFI ::ERROR_SUCCESS
307+ case type_ptr . read_dword
308+ when Win32 ::Registry ::REG_SZ , Win32 ::Registry ::REG_EXPAND_SZ
309+ buffer_ptr . put_uint16 ( base_bytes_len , 0 )
310+ when Win32 ::Registry ::REG_MULTI_SZ
311+ buffer_ptr . put_uint16 ( base_bytes_len , 0 )
312+ buffer_ptr . put_uint16 ( base_bytes_len + WCHAR_SIZE , 0 )
313+ end
314+ else
280315 # buffer is raw bytes, *not* chars - less a NULL terminator
281- name_length = ( name_ptr . size / FFI . type_size ( :wchar ) ) - 1 if name_ptr . size > 0
316+ name_length = ( name_ptr . size / WCHAR_SIZE ) - 1 if name_ptr . size > 0
282317 msg = _ ( "Failed to read registry value %{value} at %{key}" ) % { value : name_ptr . read_wide_string ( name_length ) , key : key . keyname }
283318 raise Puppet ::Util ::Windows ::Error . new ( msg , result )
284319 end
0 commit comments