Skip to content

Commit 3192f4a

Browse files
Merge pull request #9205 from joshcooper/win_reg
(PUP-11122) Ensure reg values are null terminated
2 parents 8a49cf3 + 25a33df commit 3192f4a

File tree

1 file changed

+39
-4
lines changed

1 file changed

+39
-4
lines changed

lib/puppet/util/windows/registry.rb

+39-4
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)