Skip to content

Commit a31b0f9

Browse files
committed
(MODULES-1723) Use wide character registry APIs
- Switch to using RegQueryValueExW and RegDeleteKeyExW instead of their ANSI counterparts - Since Ruby 2.1.5 is broken with respec to calling delete_key and delete_value, add our own code for RegDeleteValueW to be used. Ensure that all deletions of registry keys go through the API rather than Ruby. - Implement a helper method for converting Ruby strings to UTF-16LE strings and for creating an unmanaged LCPWSTR pointer for use in FFI calls given a Ruby string. This code appears in Puppet itself, but for compatibility reasons, the Registry module cannot rely on the Puppet version that defines it. - Implement an each_value inside of the provider base code that either uses Rubys Win32::Registry#each_value in Ruby 2.0 and lower, or uses the Puppet 4 implementation if available. This provides existing behavior for older versions of Puppet / Ruby which don't have LOCALE conversion issues, and the correct behavior for Puppet 4. This was implemented this way to avoid dragging in a lot of duplicated FFI code from Puppet 4 introduced via: puppetlabs/puppet@b46ede7
1 parent 036d074 commit a31b0f9

File tree

5 files changed

+118
-36
lines changed

5 files changed

+118
-36
lines changed

lib/puppet/provider/registry_key/registry.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ def destroy
3838

3939
raise ArgumentError, "Cannot delete root key: #{path}" unless subkey
4040

41-
# hive.hkey returns an integer value that's like a FD
42-
if RegDeleteKeyExA(hive.hkey, subkey, access, 0) != 0
43-
raise "Failed to delete registry key: #{self}"
41+
from_string_to_wide_string(subkey) do |subkey_ptr|
42+
# hive.hkey returns an integer value that's like a FD
43+
if RegDeleteKeyExW(hive.hkey, subkey_ptr, access, 0) != 0
44+
raise "Failed to delete registry key: #{self}"
45+
end
4446
end
4547
end
4648

@@ -49,7 +51,7 @@ def values
4951
# Only try and get the values for this key if the key itself exists.
5052
if exists? then
5153
hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
52-
reg.each_value do |name, type, data| names << name end
54+
each_value(reg) do |name, type, data| names << name end
5355
end
5456
end
5557
names

lib/puppet/provider/registry_value/registry.rb

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ def exists?
2424
found = false
2525
begin
2626
hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
27-
status = RegQueryValueExA(reg.hkey, valuename,
28-
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
29-
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL)
27+
from_string_to_wide_string(valuename) do |valuename_ptr|
28+
status = RegQueryValueExW(reg.hkey, valuename_ptr,
29+
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
30+
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL)
3031

31-
found = status == 0
32-
raise Win32::Registry::Error.new(status) if !found
32+
found = status == 0
33+
raise Win32::Registry::Error.new(status) if !found
34+
end
3335
end
3436
rescue Win32::Registry::Error => detail
3537
case detail.code
@@ -61,8 +63,15 @@ def flush
6163

6264
def destroy
6365
Puppet.debug("Destroying registry value: #{self}")
66+
# On Ruby 2.1.x, due to https://bugs.ruby-lang.org/issues/10820, we see
67+
# a FileNotFound error - hence an FFI re-implementation inside destroy
6468
hive.open(subkey, Win32::Registry::KEY_ALL_ACCESS | access) do |reg|
65-
reg.delete_value(valuename)
69+
from_string_to_wide_string(valuename) do |valuename_ptr|
70+
if RegDeleteValueW(reg.hkey, valuename_ptr) != 0
71+
msg = "Failed to delete registry value #{valuename} at #{reg.keyname}"
72+
raise Puppet::Util::Windows::Error.new(msg)
73+
end
74+
end
6675
end
6776
end
6877

@@ -86,10 +95,12 @@ def regvalue
8695
unless @regvalue
8796
@regvalue = {}
8897
hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
89-
if RegQueryValueExA(reg.hkey, valuename,
90-
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
91-
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL) == 0
92-
@regvalue[:type], @regvalue[:data] = from_native(reg.read(valuename))
98+
from_string_to_wide_string(valuename) do |valuename_ptr|
99+
if RegQueryValueExW(reg.hkey, valuename_ptr,
100+
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
101+
FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL) == 0
102+
@regvalue[:type], @regvalue[:data] = from_native(reg.read(valuename))
103+
end
93104
end
94105
end
95106
end

lib/puppet_x/puppetlabs/registry/provider_base.rb

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module PuppetX
33
module Puppetlabs
44
module Registry
55
module ProviderBase
6-
def self.define_ffi
6+
def self.define_ffi(base)
77
extend FFI::Library
88

99
ffi_convention :stdcall
@@ -28,7 +28,7 @@ def self.define_ffi
2828
# _Inout_opt_ LPDWORD lpcbData
2929
# );
3030
ffi_lib :advapi32
31-
attach_function :RegQueryValueExA,
31+
attach_function :RegQueryValueExW,
3232
[:handle, :pointer, :pointer, :pointer, :pointer, :pointer], :win32_long
3333

3434
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724847(v=vs.85).aspx
@@ -39,12 +39,49 @@ def self.define_ffi
3939
# _Reserved_ DWORD Reserved
4040
# );
4141
ffi_lib :advapi32
42-
attach_function :RegDeleteKeyExA,
42+
attach_function :RegDeleteKeyExW,
4343
[:handle, :pointer, :win32_ulong, :dword], :win32_long
44+
45+
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724851(v=vs.85).aspx
46+
# LONG WINAPI RegDeleteValue(
47+
# _In_ HKEY hKey,
48+
# _In_opt_ LPCTSTR lpValueName
49+
# );
50+
ffi_lib :advapi32
51+
attach_function :RegDeleteValueW,
52+
[:handle, :pointer], :win32_long
53+
54+
# this duplicates code found in puppet, but necessary for backwards compat
55+
class << base
56+
# note that :uchar is aliased in Puppet to :byte
57+
def from_string_to_wide_string(str, &block)
58+
str = wide_string(str)
59+
FFI::MemoryPointer.new(:uchar, str.bytesize) do |ptr|
60+
ptr.put_array_of_uchar(0, str.bytes.to_a)
61+
62+
yield ptr
63+
end
64+
65+
# ptr has already had free called, so nothing to return
66+
nil
67+
end
68+
69+
def wide_string(str)
70+
# if given a nil string, assume caller wants to pass a nil pointer to win32
71+
return nil if str.nil?
72+
# ruby (< 2.1) does not respect multibyte terminators, so it is possible
73+
# for a string to contain a single trailing null byte, followed by garbage
74+
# causing buffer overruns.
75+
#
76+
# See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision
77+
newstr = str + "\0".encode(str.encoding)
78+
newstr.encode!('UTF-16LE')
79+
end
80+
end
4481
end
4582

4683
# This is a class method in order to be easily mocked in the spec tests.
47-
def self.initialize_system_api
84+
def self.initialize_system_api(base)
4885
if Puppet.features.microsoft_windows?
4986
begin
5087
require 'win32/registry'
@@ -58,22 +95,30 @@ def self.initialize_system_api
5895

5996
begin
6097
require 'ffi'
61-
define_ffi
98+
define_ffi(base)
6299
rescue LoadError => exc
63100
msg = "Could not load the required ffi library [#{exc.message}]"
64101
Puppet.err msg
65102
error = Puppet::Error.new(msg)
66103
error.set_backtrace exc.backtrace
67104
raise error
68105
end
106+
107+
class << base
108+
# create instance to access mix-in methods since it doesn't use module_function
109+
require 'puppet/util/windows/registry'
110+
def RegistryHelpers
111+
@registry_helpers ||= Class.new.extend(Puppet::Util::Windows::Registry)
112+
end
113+
end
69114
end
70115
end
71116

72117
def self.included(base)
73118
# Initialize the Win32 API. This is a method call so the spec tests can
74119
# easily mock the initialization of the Win32 libraries on non-win32
75120
# systems.
76-
initialize_system_api
121+
initialize_system_api(base)
77122

78123
# Define an hkeys class method in the eigenclass we're being mixed into.
79124
# This is the one true place to define the root hives we support.
@@ -91,10 +136,18 @@ def hkeys
91136
# The rest of these methods will be mixed in as instance methods into the
92137
# provider class. The path method is expected to be mixed in by the provider
93138
# specific module, ProviderKeyBase or ProviderValueBase
139+
def from_string_to_wide_string(str, &block)
140+
self.class.from_string_to_wide_string(str, &block)
141+
end
142+
94143
def hkeys
95144
self.class.hkeys
96145
end
97146

147+
def registry_helpers
148+
self.class.RegistryHelpers
149+
end
150+
98151
def hive
99152
hkeys[path.root]
100153
end
@@ -136,6 +189,22 @@ def name2type(name)
136189
type2name_map.each_pair {|k,v| name2type[v] = k}
137190
name2type[name]
138191
end
192+
193+
def each_value(key, &block)
194+
# This problem affects Ruby 2.1 and higher by introducing locale conversion
195+
# unnecessary. Puppet 4 introduces it's own each_value patches to the
196+
# Registry abstraction to work around these problems
197+
# https://github.com/puppetlabs/puppet/commit/b46ede74f640a809b68a473ac8720b93b13d2ac3
198+
if registry_helpers.respond_to?(:each_value)
199+
registry_helpers.each_value(key) do |name, type, data|
200+
yield name, type, data
201+
end
202+
else
203+
key.each_value do |name, type, data|
204+
yield name, type, data
205+
end
206+
end
207+
end
139208
end
140209
end
141210
end

spec/unit/puppet/provider/registry_key_spec.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111
subkey_name ="PuppetRegProviderTest"
1212

1313
before(:each) do
14-
if RUBY_VERSION >= '2.1'
15-
# problematic Ruby codepath triggers a conversion of UTF-16LE to
16-
# a local codepage which can totally break when that codepage has no
17-
# conversion from the given UTF-16LE characters to local codepage
18-
# a prime example is that IBM437 has no conversion from a Unicode en-dash
19-
Win32::Registry.any_instance.expects(:export_string).never
14+
# problematic Ruby codepath triggers a conversion of UTF-16LE to
15+
# a local codepage which can totally break when that codepage has no
16+
# conversion from the given UTF-16LE characters to local codepage
17+
# a prime example is that IBM437 has no conversion from a Unicode en-dash
18+
Win32::Registry.any_instance.expects(:export_string).never
2019

21-
Win32::Registry.any_instance.expects(:delete_value).never
22-
Win32::Registry.any_instance.expects(:delete_key).never
20+
Win32::Registry.any_instance.expects(:delete_value).never
21+
Win32::Registry.any_instance.expects(:delete_key).never
2322

23+
if RUBY_VERSION >= '2.1'
2424
# also, expect that we're not using Rubys each_key / each_value which exhibit bad behavior
2525
Win32::Registry.any_instance.expects(:each_key).never
2626
Win32::Registry.any_instance.expects(:each_value).never

spec/unit/puppet/provider/registry_value_spec.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
end
1818

1919
before(:each) do
20-
if RUBY_VERSION >= '2.1'
21-
# problematic Ruby codepath triggers a conversion of UTF-16LE to
22-
# a local codepage which can totally break when that codepage has no
23-
# conversion from the given UTF-16LE characters to local codepage
24-
# a prime example is that IBM437 has no conversion from a Unicode en-dash
25-
Win32::Registry.any_instance.expects(:export_string).never
20+
# problematic Ruby codepath triggers a conversion of UTF-16LE to
21+
# a local codepage which can totally break when that codepage has no
22+
# conversion from the given UTF-16LE characters to local codepage
23+
# a prime example is that IBM437 has no conversion from a Unicode en-dash
24+
Win32::Registry.any_instance.expects(:export_string).never
2625

27-
Win32::Registry.any_instance.expects(:delete_value).never
28-
Win32::Registry.any_instance.expects(:delete_key).never
26+
Win32::Registry.any_instance.expects(:delete_value).never
27+
Win32::Registry.any_instance.expects(:delete_key).never
2928

29+
if RUBY_VERSION >= '2.1'
3030
# also, expect that we're not using Rubys each_key / each_value which exhibit bad behavior
3131
Win32::Registry.any_instance.expects(:each_key).never
3232
Win32::Registry.any_instance.expects(:each_value).never

0 commit comments

Comments
 (0)