puppet 2.7.21 → 2.7.22
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/CHANGELOG +14 -0
- data/Gemfile.lock +2 -2
- data/ext/build_defaults.yaml +2 -3
- data/ext/debian/control +1 -1
- data/ext/packaging/README.md +496 -8
- data/ext/packaging/spec/tasks/00_utils_spec.rb +7 -7
- data/ext/packaging/spec/tasks/build_object_spec.rb +3 -0
- data/ext/packaging/tasks/00_utils.rake +2 -2
- data/ext/packaging/tasks/10_setupvars.rake +8 -1
- data/ext/packaging/tasks/build.rake +2 -0
- data/ext/packaging/tasks/deb_repos.rake +48 -15
- data/ext/packaging/tasks/jenkins.rake +30 -2
- data/ext/packaging/tasks/mock.rake +3 -2
- data/ext/packaging/tasks/pe_remote.rake +1 -1
- data/ext/packaging/tasks/pe_ship.rake +4 -5
- data/ext/packaging/tasks/pe_sign.rake +8 -0
- data/ext/packaging/tasks/pe_sles.rake +8 -7
- data/ext/packaging/tasks/pre_tasks.rake +0 -0
- data/ext/packaging/tasks/retrieve.rake +11 -1
- data/ext/packaging/tasks/rpm_repos.rake +71 -49
- data/ext/packaging/tasks/ship.rake +14 -2
- data/ext/packaging/tasks/sign.rake +9 -3
- data/ext/packaging/tasks/tar.rake +5 -0
- data/ext/packaging/tasks/vendor_gems.rake +110 -0
- data/install.rb +1 -1
- data/lib/puppet.rb +11 -0
- data/lib/puppet/indirector/report/processor.rb +1 -1
- data/lib/puppet/indirector/report/rest.rb +7 -0
- data/lib/puppet/indirector/resource/rest.rb +9 -0
- data/lib/puppet/indirector/rest.rb +81 -47
- data/lib/puppet/indirector/run/rest.rb +6 -0
- data/lib/puppet/network/formats.rb +20 -10
- data/lib/puppet/network/http/handler.rb +1 -1
- data/lib/puppet/node.rb +25 -0
- data/lib/puppet/node/facts.rb +23 -4
- data/lib/puppet/resource.rb +2 -4
- data/lib/puppet/resource/status.rb +28 -0
- data/lib/puppet/run.rb +24 -2
- data/lib/puppet/status.rb +6 -2
- data/lib/puppet/transaction/event.rb +19 -0
- data/lib/puppet/transaction/report.rb +39 -0
- data/lib/puppet/util/log.rb +19 -0
- data/lib/puppet/util/metric.rb +6 -0
- data/lib/puppet/util/monkey_patches.rb +0 -16
- data/lib/puppet/vendor.rb +55 -0
- data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
- data/lib/puppet/vendor/require_vendored.rb +4 -0
- data/lib/puppet/vendor/safe_yaml/CHANGES.md +104 -0
- data/lib/puppet/vendor/safe_yaml/Gemfile +11 -0
- data/lib/puppet/vendor/safe_yaml/LICENSE.txt +22 -0
- data/lib/puppet/vendor/safe_yaml/README.md +179 -0
- data/lib/puppet/vendor/safe_yaml/Rakefile +6 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb +253 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb +34 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb +27 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb +12 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb +26 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb +92 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb +52 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb +94 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb +36 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb +43 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb +38 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb +41 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb +21 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb +11 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb +33 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb +25 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb +18 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb +13 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb +47 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb +3 -0
- data/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh +21 -0
- data/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec +18 -0
- data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml +2 -0
- data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml +2 -0
- data/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb +10 -0
- data/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb +250 -0
- data/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb +702 -0
- data/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb +18 -0
- data/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb +29 -0
- data/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb +10 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb +11 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb +34 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb +42 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb +59 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb +49 -0
- data/lib/puppet/version.rb +1 -1
- data/spec/lib/puppet_spec/matchers.rb +8 -0
- data/spec/unit/file_serving/metadata_spec.rb +20 -28
- data/spec/unit/indirector/report/rest_spec.rb +41 -0
- data/spec/unit/indirector/rest_spec.rb +314 -339
- data/spec/unit/network/formats_spec.rb +36 -27
- data/spec/unit/network/http/handler_spec.rb +3 -12
- data/spec/unit/node_spec.rb +81 -0
- data/spec/unit/resource_spec.rb +5 -35
- data/spec/unit/run_spec.rb +22 -8
- data/spec/unit/status_spec.rb +6 -0
- data/test/network/handler/report.rb +0 -36
- metadata +148 -102
@@ -0,0 +1,702 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require "exploitable_back_door"
|
4
|
+
|
5
|
+
describe YAML do
|
6
|
+
# Essentially stolen from:
|
7
|
+
# https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L10-25
|
8
|
+
def silence_warnings
|
9
|
+
$VERBOSE = nil; yield
|
10
|
+
ensure
|
11
|
+
$VERBOSE = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def safe_load_round_trip(object, options={})
|
15
|
+
yaml = object.to_yaml
|
16
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
17
|
+
YAML.safe_load(yaml, nil, options)
|
18
|
+
else
|
19
|
+
YAML.safe_load(yaml, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
SafeYAML.restore_defaults!
|
25
|
+
end
|
26
|
+
|
27
|
+
after :each do
|
28
|
+
SafeYAML.restore_defaults!
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "unsafe_load" do
|
32
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
33
|
+
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
34
|
+
backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
35
|
+
backdoor.should be_exploited_through_setter
|
36
|
+
end
|
37
|
+
|
38
|
+
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
39
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
40
|
+
backdoor.should be_exploited_through_init_with
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
45
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
46
|
+
backdoor.should be_exploited_through_ivars
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with special whitelisted tags defined" do
|
50
|
+
before :each do
|
51
|
+
SafeYAML::whitelist!(OpenStruct)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "effectively ignores the whitelist (since everything is whitelisted)" do
|
55
|
+
result = YAML.unsafe_load <<-YAML.unindent
|
56
|
+
--- !ruby/object:OpenStruct
|
57
|
+
table:
|
58
|
+
:backdoor: !ruby/object:ExploitableBackDoor
|
59
|
+
foo: bar
|
60
|
+
YAML
|
61
|
+
|
62
|
+
result.should be_a(OpenStruct)
|
63
|
+
result.backdoor.should be_exploited_through_ivars
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "safe_load" do
|
69
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
70
|
+
object = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
71
|
+
object.should_not be_a(ExploitableBackDoor)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
75
|
+
object = YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
76
|
+
object.should_not be_a(ExploitableBackDoor)
|
77
|
+
end
|
78
|
+
|
79
|
+
context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
|
80
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
81
|
+
let(:options) { nil }
|
82
|
+
let(:arguments) { ["foo: bar", nil, options] }
|
83
|
+
|
84
|
+
context "when no tags are whitelisted" do
|
85
|
+
it "constructs a SafeYAML::PsychHandler to resolve nodes as they're parsed, for optimal performance" do
|
86
|
+
Psych::Parser.should_receive(:new).with an_instance_of(SafeYAML::PsychHandler)
|
87
|
+
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
88
|
+
YAML.safe_load(*arguments) rescue nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when whitelisted tags are specified" do
|
93
|
+
let(:options) {
|
94
|
+
{ :whitelisted_tags => ["foo"] }
|
95
|
+
}
|
96
|
+
|
97
|
+
it "instead uses Psych to construct a full tree before examining the nodes" do
|
98
|
+
Psych.should_receive(:parse)
|
99
|
+
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
100
|
+
YAML.safe_load(*arguments) rescue nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if SafeYAML::YAML_ENGINE == "syck"
|
106
|
+
it "uses Syck internally to parse YAML" do
|
107
|
+
YAML.should_receive(:parse).with("foo: bar")
|
108
|
+
# This won't work now; we just want to ensure YAML::parse was in fact called.
|
109
|
+
YAML.safe_load("foo: bar") rescue nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "loads a plain ol' YAML document just fine" do
|
115
|
+
result = YAML.safe_load <<-YAML.unindent
|
116
|
+
foo:
|
117
|
+
number: 1
|
118
|
+
string: Hello, there!
|
119
|
+
symbol: :blah
|
120
|
+
sequence:
|
121
|
+
- hi
|
122
|
+
- bye
|
123
|
+
YAML
|
124
|
+
|
125
|
+
result.should == {
|
126
|
+
"foo" => {
|
127
|
+
"number" => 1,
|
128
|
+
"string" => "Hello, there!",
|
129
|
+
"symbol" => ":blah",
|
130
|
+
"sequence" => ["hi", "bye"]
|
131
|
+
}
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "works for YAML documents with anchors and aliases" do
|
136
|
+
result = YAML.safe_load <<-YAML
|
137
|
+
- &id001 {}
|
138
|
+
- *id001
|
139
|
+
- *id001
|
140
|
+
YAML
|
141
|
+
|
142
|
+
result.should == [{}, {}, {}]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "works for YAML documents with binary tagged keys" do
|
146
|
+
result = YAML.safe_load <<-YAML
|
147
|
+
? !!binary >
|
148
|
+
Zm9v
|
149
|
+
: "bar"
|
150
|
+
? !!binary >
|
151
|
+
YmFy
|
152
|
+
: "baz"
|
153
|
+
YAML
|
154
|
+
|
155
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
156
|
+
end
|
157
|
+
|
158
|
+
it "works for YAML documents with binary tagged values" do
|
159
|
+
result = YAML.safe_load <<-YAML
|
160
|
+
"foo": !!binary >
|
161
|
+
YmFy
|
162
|
+
"bar": !!binary >
|
163
|
+
YmF6
|
164
|
+
YAML
|
165
|
+
|
166
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
167
|
+
end
|
168
|
+
|
169
|
+
it "works for YAML documents with binary tagged array values" do
|
170
|
+
result = YAML.safe_load <<-YAML
|
171
|
+
- !binary |-
|
172
|
+
Zm9v
|
173
|
+
- !binary |-
|
174
|
+
YmFy
|
175
|
+
YAML
|
176
|
+
|
177
|
+
result.should == ["foo", "bar"]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "works for YAML documents with sections" do
|
181
|
+
result = YAML.safe_load <<-YAML
|
182
|
+
mysql: &mysql
|
183
|
+
adapter: mysql
|
184
|
+
pool: 30
|
185
|
+
login: &login
|
186
|
+
username: user
|
187
|
+
password: password123
|
188
|
+
development: &development
|
189
|
+
<<: *mysql
|
190
|
+
<<: *login
|
191
|
+
host: localhost
|
192
|
+
YAML
|
193
|
+
|
194
|
+
result.should == {
|
195
|
+
"mysql" => {
|
196
|
+
"adapter" => "mysql",
|
197
|
+
"pool" => 30
|
198
|
+
},
|
199
|
+
"login" => {
|
200
|
+
"username" => "user",
|
201
|
+
"password" => "password123"
|
202
|
+
},
|
203
|
+
"development" => {
|
204
|
+
"adapter" => "mysql",
|
205
|
+
"pool" => 30,
|
206
|
+
"username" => "user",
|
207
|
+
"password" => "password123",
|
208
|
+
"host" => "localhost"
|
209
|
+
}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
it "correctly prefers explicitly defined values over default values from included sections" do
|
214
|
+
# Repeating this test 100 times to increase the likelihood of running into an issue caused by
|
215
|
+
# non-deterministic hash key enumeration.
|
216
|
+
100.times do
|
217
|
+
result = YAML.safe_load <<-YAML
|
218
|
+
defaults: &defaults
|
219
|
+
foo: foo
|
220
|
+
bar: bar
|
221
|
+
baz: baz
|
222
|
+
custom:
|
223
|
+
<<: *defaults
|
224
|
+
bar: custom_bar
|
225
|
+
baz: custom_baz
|
226
|
+
YAML
|
227
|
+
|
228
|
+
result["custom"].should == {
|
229
|
+
"foo" => "foo",
|
230
|
+
"bar" => "custom_bar",
|
231
|
+
"baz" => "custom_baz"
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it "works with multi-level inheritance" do
|
237
|
+
result = YAML.safe_load <<-YAML
|
238
|
+
defaults: &defaults
|
239
|
+
foo: foo
|
240
|
+
bar: bar
|
241
|
+
baz: baz
|
242
|
+
custom: &custom
|
243
|
+
<<: *defaults
|
244
|
+
bar: custom_bar
|
245
|
+
baz: custom_baz
|
246
|
+
grandcustom: &grandcustom
|
247
|
+
<<: *custom
|
248
|
+
YAML
|
249
|
+
|
250
|
+
result.should == {
|
251
|
+
"defaults" => { "foo" => "foo", "bar" => "bar", "baz" => "baz" },
|
252
|
+
"custom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" },
|
253
|
+
"grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
it "returns false when parsing an empty document" do
|
258
|
+
result = YAML.safe_load ""
|
259
|
+
result.should == false
|
260
|
+
end
|
261
|
+
|
262
|
+
context "with custom initializers defined" do
|
263
|
+
before :each do
|
264
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
265
|
+
SafeYAML::OPTIONS[:custom_initializers] = {
|
266
|
+
"!set" => lambda { Set.new },
|
267
|
+
"!hashiemash" => lambda { Hashie::Mash.new }
|
268
|
+
}
|
269
|
+
else
|
270
|
+
SafeYAML::OPTIONS[:custom_initializers] = {
|
271
|
+
"tag:yaml.org,2002:set" => lambda { Set.new },
|
272
|
+
"tag:yaml.org,2002:hashiemash" => lambda { Hashie::Mash.new }
|
273
|
+
}
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
it "will use a custom initializer to instantiate an array-like class upon deserialization" do
|
278
|
+
result = YAML.safe_load <<-YAML.unindent
|
279
|
+
--- !set
|
280
|
+
- 1
|
281
|
+
- 2
|
282
|
+
- 3
|
283
|
+
YAML
|
284
|
+
|
285
|
+
result.should be_a(Set)
|
286
|
+
result.to_a.should =~ [1, 2, 3]
|
287
|
+
end
|
288
|
+
|
289
|
+
it "will use a custom initializer to instantiate a hash-like class upon deserialization" do
|
290
|
+
result = YAML.safe_load <<-YAML.unindent
|
291
|
+
--- !hashiemash
|
292
|
+
foo: bar
|
293
|
+
YAML
|
294
|
+
|
295
|
+
result.should be_a(Hashie::Mash)
|
296
|
+
result.to_hash.should == { "foo" => "bar" }
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context "with special whitelisted tags defined" do
|
301
|
+
before :each do
|
302
|
+
SafeYAML::whitelist!(OpenStruct)
|
303
|
+
|
304
|
+
# Necessary for deserializing OpenStructs properly.
|
305
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = true
|
306
|
+
end
|
307
|
+
|
308
|
+
it "will allow objects to be deserialized for whitelisted tags" do
|
309
|
+
result = YAML.safe_load("--- !ruby/object:OpenStruct\ntable:\n foo: bar\n")
|
310
|
+
result.should be_a(OpenStruct)
|
311
|
+
result.instance_variable_get(:@table).should == { "foo" => "bar" }
|
312
|
+
end
|
313
|
+
|
314
|
+
it "will not deserialize objects without whitelisted tags" do
|
315
|
+
result = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
316
|
+
result.should_not be_a(ExploitableBackDoor)
|
317
|
+
result.should == { "foo" => "bar" }
|
318
|
+
end
|
319
|
+
|
320
|
+
it "will not allow non-whitelisted objects to be embedded within objects with whitelisted tags" do
|
321
|
+
result = YAML.safe_load <<-YAML.unindent
|
322
|
+
--- !ruby/object:OpenStruct
|
323
|
+
table:
|
324
|
+
:backdoor: !ruby/object:ExploitableBackDoor
|
325
|
+
foo: bar
|
326
|
+
YAML
|
327
|
+
|
328
|
+
result.should be_a(OpenStruct)
|
329
|
+
result.backdoor.should_not be_a(ExploitableBackDoor)
|
330
|
+
result.backdoor.should == { "foo" => "bar" }
|
331
|
+
end
|
332
|
+
|
333
|
+
context "with the :raise_on_unknown_tag option enabled" do
|
334
|
+
before :each do
|
335
|
+
SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
|
336
|
+
end
|
337
|
+
|
338
|
+
after :each do
|
339
|
+
SafeYAML.restore_defaults!
|
340
|
+
end
|
341
|
+
|
342
|
+
it "raises an exception if a non-nil, non-whitelisted tag is encountered" do
|
343
|
+
lambda {
|
344
|
+
YAML.safe_load <<-YAML.unindent
|
345
|
+
--- !ruby/object:Unknown
|
346
|
+
foo: bar
|
347
|
+
YAML
|
348
|
+
}.should raise_error
|
349
|
+
end
|
350
|
+
|
351
|
+
it "checks all tags, even those within objects with trusted tags" do
|
352
|
+
lambda {
|
353
|
+
YAML.safe_load <<-YAML.unindent
|
354
|
+
--- !ruby/object:OpenStruct
|
355
|
+
table:
|
356
|
+
:backdoor: !ruby/object:Unknown
|
357
|
+
foo: bar
|
358
|
+
YAML
|
359
|
+
}.should raise_error
|
360
|
+
end
|
361
|
+
|
362
|
+
it "does not raise an exception as long as all tags are whitelisted" do
|
363
|
+
result = YAML.safe_load <<-YAML.unindent
|
364
|
+
--- !ruby/object:OpenStruct
|
365
|
+
table:
|
366
|
+
:backdoor:
|
367
|
+
string: foo
|
368
|
+
integer: 1
|
369
|
+
float: 3.14
|
370
|
+
symbol: :bar
|
371
|
+
date: 2013-02-20
|
372
|
+
array: []
|
373
|
+
hash: {}
|
374
|
+
YAML
|
375
|
+
|
376
|
+
result.should be_a(OpenStruct)
|
377
|
+
result.backdoor.should == {
|
378
|
+
"string" => "foo",
|
379
|
+
"integer" => 1,
|
380
|
+
"float" => 3.14,
|
381
|
+
"symbol" => :bar,
|
382
|
+
"date" => Date.parse("2013-02-20"),
|
383
|
+
"array" => [],
|
384
|
+
"hash" => {}
|
385
|
+
}
|
386
|
+
end
|
387
|
+
|
388
|
+
it "does not raise an exception on the non-specific '!' tag" do
|
389
|
+
result = nil
|
390
|
+
expect { result = YAML.safe_load "--- ! 'foo'" }.to_not raise_error
|
391
|
+
result.should == "foo"
|
392
|
+
end
|
393
|
+
|
394
|
+
context "with whitelisted custom class" do
|
395
|
+
class SomeClass
|
396
|
+
attr_accessor :foo
|
397
|
+
end
|
398
|
+
let(:instance) { SomeClass.new }
|
399
|
+
|
400
|
+
before do
|
401
|
+
SafeYAML::whitelist!(SomeClass)
|
402
|
+
instance.foo = 'with trailing whitespace: '
|
403
|
+
end
|
404
|
+
|
405
|
+
it "does not raise an exception on the non-specific '!' tag" do
|
406
|
+
result = nil
|
407
|
+
expect { result = YAML.safe_load(instance.to_yaml) }.to_not raise_error
|
408
|
+
result.foo.should == 'with trailing whitespace: '
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
context "when options are passed direclty to #load which differ from the defaults" do
|
415
|
+
let(:default_options) { {} }
|
416
|
+
|
417
|
+
before :each do
|
418
|
+
SafeYAML::OPTIONS.merge!(default_options)
|
419
|
+
end
|
420
|
+
|
421
|
+
context "(for example, when symbol deserialization is enabled by default)" do
|
422
|
+
let(:default_options) { { :deserialize_symbols => true } }
|
423
|
+
|
424
|
+
it "goes with the default option when it is not overridden" do
|
425
|
+
silence_warnings do
|
426
|
+
YAML.load(":foo: bar").should == { :foo => "bar" }
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
it "allows the default option to be overridden on a per-call basis" do
|
431
|
+
silence_warnings do
|
432
|
+
YAML.load(":foo: bar", :deserialize_symbols => false).should == { ":foo" => "bar" }
|
433
|
+
YAML.load(":foo: bar", :deserialize_symbols => true).should == { :foo => "bar" }
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
context "(or, for example, when certain tags are whitelisted)" do
|
439
|
+
let(:default_options) {
|
440
|
+
{
|
441
|
+
:deserialize_symbols => true,
|
442
|
+
:whitelisted_tags => SafeYAML::YAML_ENGINE == "psych" ?
|
443
|
+
["!ruby/object:OpenStruct"] :
|
444
|
+
["tag:ruby.yaml.org,2002:object:OpenStruct"]
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
448
|
+
it "goes with the default option when it is not overridden" do
|
449
|
+
result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
|
450
|
+
result.should be_a(OpenStruct)
|
451
|
+
result.foo.should == "bar"
|
452
|
+
end
|
453
|
+
|
454
|
+
it "allows the default option to be overridden on a per-call basis" do
|
455
|
+
result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :whitelisted_tags => [])
|
456
|
+
result.should == { "table" => { :foo => "bar" } }
|
457
|
+
|
458
|
+
result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :deserialize_symbols => false, :whitelisted_tags => [])
|
459
|
+
result.should == { "table" => { ":foo" => "bar" } }
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
describe "unsafe_load_file" do
|
466
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
467
|
+
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
468
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
|
469
|
+
backdoor.should be_exploited_through_setter
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
|
474
|
+
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
475
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
476
|
+
backdoor.should be_exploited_through_init_with
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
481
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
482
|
+
backdoor.should be_exploited_through_ivars
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe "safe_load_file" do
|
487
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
488
|
+
object = YAML.safe_load_file "spec/exploit.1.9.3.yaml"
|
489
|
+
object.should_not be_a(ExploitableBackDoor)
|
490
|
+
end
|
491
|
+
|
492
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
493
|
+
object = YAML.safe_load_file "spec/exploit.1.9.2.yaml"
|
494
|
+
object.should_not be_a(ExploitableBackDoor)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
describe "load" do
|
499
|
+
let(:options) { {} }
|
500
|
+
|
501
|
+
let (:arguments) {
|
502
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
503
|
+
["foo: bar", nil, options]
|
504
|
+
else
|
505
|
+
["foo: bar", options]
|
506
|
+
end
|
507
|
+
}
|
508
|
+
|
509
|
+
context "as long as a :default_mode has been specified" do
|
510
|
+
it "doesn't issue a warning for safe mode, since an explicit mode has been set" do
|
511
|
+
SafeYAML::OPTIONS[:default_mode] = :safe
|
512
|
+
Kernel.should_not_receive(:warn)
|
513
|
+
YAML.load(*arguments)
|
514
|
+
end
|
515
|
+
|
516
|
+
it "doesn't issue a warning for unsafe mode, since an explicit mode has been set" do
|
517
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
518
|
+
Kernel.should_not_receive(:warn)
|
519
|
+
YAML.load(*arguments)
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
context "when the :safe options is specified" do
|
524
|
+
let(:safe_mode) { true }
|
525
|
+
let(:options) { { :safe => safe_mode } }
|
526
|
+
|
527
|
+
it "doesn't issue a warning" do
|
528
|
+
Kernel.should_not_receive(:warn)
|
529
|
+
YAML.load(*arguments)
|
530
|
+
end
|
531
|
+
|
532
|
+
it "calls #safe_load if the :safe option is set to true" do
|
533
|
+
YAML.should_receive(:safe_load)
|
534
|
+
YAML.load(*arguments)
|
535
|
+
end
|
536
|
+
|
537
|
+
context "when the :safe option is set to false" do
|
538
|
+
let(:safe_mode) { false }
|
539
|
+
|
540
|
+
it "calls #unsafe_load if the :safe option is set to false" do
|
541
|
+
YAML.should_receive(:unsafe_load)
|
542
|
+
YAML.load(*arguments)
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
it "issues a warning when the :safe option is omitted" do
|
548
|
+
silence_warnings do
|
549
|
+
Kernel.should_receive(:warn)
|
550
|
+
YAML.load(*arguments)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
it "only issues a warning once (to avoid spamming an app's output)" do
|
555
|
+
silence_warnings do
|
556
|
+
Kernel.should_receive(:warn).once
|
557
|
+
2.times { YAML.load(*arguments) }
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
562
|
+
silence_warnings do
|
563
|
+
YAML.should_receive(:safe_load)
|
564
|
+
YAML.load(*arguments)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
context "with the default mode set to :unsafe" do
|
569
|
+
before :each do
|
570
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
571
|
+
end
|
572
|
+
|
573
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
574
|
+
silence_warnings do
|
575
|
+
YAML.should_receive(:unsafe_load)
|
576
|
+
YAML.load(*arguments)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
it "calls #safe_load if the :safe option is set to true" do
|
581
|
+
YAML.should_receive(:safe_load)
|
582
|
+
YAML.load(*(arguments + [{ :safe => true }]))
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
describe "load_file" do
|
588
|
+
let(:filename) { "spec/exploit.1.9.2.yaml" } # doesn't really matter
|
589
|
+
|
590
|
+
it "issues a warning if the :safe option is omitted" do
|
591
|
+
silence_warnings do
|
592
|
+
Kernel.should_receive(:warn)
|
593
|
+
YAML.load_file(filename)
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
it "doesn't issue a warning as long as the :safe option is specified" do
|
598
|
+
Kernel.should_not_receive(:warn)
|
599
|
+
YAML.load_file(filename, :safe => true)
|
600
|
+
end
|
601
|
+
|
602
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
603
|
+
silence_warnings do
|
604
|
+
YAML.should_receive(:safe_load_file)
|
605
|
+
YAML.load_file(filename)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
it "calls #safe_load_file if the :safe option is set to true" do
|
610
|
+
YAML.should_receive(:safe_load_file)
|
611
|
+
YAML.load_file(filename, :safe => true)
|
612
|
+
end
|
613
|
+
|
614
|
+
it "calls #unsafe_load_file if the :safe option is set to false" do
|
615
|
+
YAML.should_receive(:unsafe_load_file)
|
616
|
+
YAML.load_file(filename, :safe => false)
|
617
|
+
end
|
618
|
+
|
619
|
+
context "with arbitrary object deserialization enabled by default" do
|
620
|
+
before :each do
|
621
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
622
|
+
end
|
623
|
+
|
624
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
625
|
+
silence_warnings do
|
626
|
+
YAML.should_receive(:unsafe_load_file)
|
627
|
+
YAML.load_file(filename)
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
it "calls #safe_load if the :safe option is set to true" do
|
632
|
+
YAML.should_receive(:safe_load_file)
|
633
|
+
YAML.load_file(filename, :safe => true)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
describe "whitelist!" do
|
639
|
+
context "not a class" do
|
640
|
+
it "should raise" do
|
641
|
+
expect { SafeYAML::whitelist! :foo }.to raise_error(/not a Class/)
|
642
|
+
SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
context "anonymous class" do
|
647
|
+
it "should raise" do
|
648
|
+
expect { SafeYAML::whitelist! Class.new }.to raise_error(/cannot be anonymous/)
|
649
|
+
SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
context "with a Class as its argument" do
|
654
|
+
it "should configure correctly" do
|
655
|
+
expect { SafeYAML::whitelist! OpenStruct }.to_not raise_error
|
656
|
+
SafeYAML::OPTIONS[:whitelisted_tags].grep(/OpenStruct\Z/).should_not be_empty
|
657
|
+
end
|
658
|
+
|
659
|
+
it "successfully deserializes the specified class" do
|
660
|
+
SafeYAML.whitelist!(OpenStruct)
|
661
|
+
|
662
|
+
# necessary for properly assigning OpenStruct attributes
|
663
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = true
|
664
|
+
|
665
|
+
result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
|
666
|
+
result.should be_a(OpenStruct)
|
667
|
+
result.foo.should == "bar"
|
668
|
+
end
|
669
|
+
|
670
|
+
it "works for ranges" do
|
671
|
+
SafeYAML.whitelist!(Range)
|
672
|
+
safe_load_round_trip(1..10).should == (1..10)
|
673
|
+
end
|
674
|
+
|
675
|
+
it "works for regular expressions" do
|
676
|
+
SafeYAML.whitelist!(Regexp)
|
677
|
+
safe_load_round_trip(/foo/).should == /foo/
|
678
|
+
end
|
679
|
+
|
680
|
+
it "works for multiple classes" do
|
681
|
+
SafeYAML.whitelist!(Range, Regexp)
|
682
|
+
safe_load_round_trip([(1..10), /bar/]).should == [(1..10), /bar/]
|
683
|
+
end
|
684
|
+
|
685
|
+
it "works for arbitrary Exception subclasses" do
|
686
|
+
class CustomException < Exception
|
687
|
+
attr_reader :custom_message
|
688
|
+
|
689
|
+
def initialize(custom_message)
|
690
|
+
@custom_message = custom_message
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
SafeYAML.whitelist!(CustomException)
|
695
|
+
|
696
|
+
ex = safe_load_round_trip(CustomException.new("blah"))
|
697
|
+
ex.should be_a(CustomException)
|
698
|
+
ex.custom_message.should == "blah"
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|
702
|
+
end
|