@@ -17,6 +17,8 @@ module Registry
17
17
18
18
ERROR_NO_MORE_ITEMS = 259
19
19
20
+ WCHAR_SIZE = FFI . type_size ( :wchar )
21
+
20
22
def root ( name )
21
23
Win32 ::Registry . const_get ( name )
22
24
rescue NameError
@@ -234,7 +236,7 @@ def read(key, name_ptr, *rtype)
234
236
235
237
string_length = 0
236
238
# 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
238
240
239
241
begin
240
242
result = case type
@@ -271,14 +273,47 @@ def query_value_ex(key, name_ptr, &block)
271
273
FFI ::Pointer ::NULL , type_ptr ,
272
274
FFI ::Pointer ::NULL , length_ptr )
273
275
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 |
275
301
result = RegQueryValueExW ( key . hkey , name_ptr ,
276
302
FFI ::Pointer ::NULL , type_ptr ,
277
303
buffer_ptr , length_ptr )
278
304
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
280
315
# 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
282
317
msg = _ ( "Failed to read registry value %{value} at %{key}" ) % { value : name_ptr . read_wide_string ( name_length ) , key : key . keyname }
283
318
raise Puppet ::Util ::Windows ::Error . new ( msg , result )
284
319
end
0 commit comments