Skip to content

Commit f403dd5

Browse files
committed
[GR-18163] Fix Marshal.load when dump is "too short"
PullRequest: truffleruby/3872
2 parents 7e54f1d + 03d38be commit f403dd5

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Bug fixes:
77

88
* Fix `Dir.glob` returning blank string entry with leading `**/` in glob and `base:` argument (@rwstauner).
99
* Fix class lookup after an object's class has been replaced by `IO#reopen` (@itarato, @eregon).
10+
* Fix `Marshal.load` and raise `ArgumentError` when dump is broken and is too short (#3108, @andrykonchin).
1011

1112
Compatibility:
1213

spec/ruby/core/marshal/shared/load.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,14 @@
623623
value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
624624
value.should == [*expected, expected[0]]
625625
end
626+
627+
it "raises ArgumentError when end of byte sequence reached before symbol characters end" do
628+
Marshal.dump(:hello).should == "\x04\b:\nhello"
629+
630+
-> {
631+
Marshal.send(@method, "\x04\b:\nhel")
632+
}.should raise_error(ArgumentError, "marshal data too short")
633+
end
626634
end
627635

628636
describe "for a String" do
@@ -685,6 +693,14 @@ def io.binmode; raise "binmode"; end
685693
result.encoding.should == Encoding::BINARY
686694
result.should == str
687695
end
696+
697+
it "raises ArgumentError when end of byte sequence reached before string characters end" do
698+
Marshal.dump("hello").should == "\x04\b\"\nhello"
699+
700+
-> {
701+
Marshal.send(@method, "\x04\b\"\nhel")
702+
}.should raise_error(ArgumentError, "marshal data too short")
703+
end
688704
end
689705

690706
describe "for a Struct" do
@@ -819,6 +835,14 @@ def io.binmode; raise "binmode"; end
819835
Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
820836
end.should raise_error(ArgumentError)
821837
end
838+
839+
it "raises ArgumentError when end of byte sequence reached before class name end" do
840+
Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00"
841+
842+
-> {
843+
Marshal.send(@method, "\x04\bo:\vObj")
844+
}.should raise_error(ArgumentError, "marshal data too short")
845+
end
822846
end
823847

824848
describe "for an object responding to #marshal_dump and #marshal_load" do
@@ -908,6 +932,14 @@ def io.binmode; raise "binmode"; end
908932
regexp.encoding.should == Encoding::UTF_32LE
909933
regexp.source.should == "a".encode("utf-32le")
910934
end
935+
936+
it "raises ArgumentError when end of byte sequence reached before source string end" do
937+
Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF"
938+
939+
-> {
940+
Marshal.send(@method, "\x04\bI/\x10hel")
941+
}.should raise_error(ArgumentError, "marshal data too short")
942+
end
911943
end
912944

913945
describe "for a Float" do
@@ -929,6 +961,14 @@ def io.binmode; raise "binmode"; end
929961
obj = 1.1867345e+22
930962
Marshal.send(@method, "\004\bf\0361.1867344999999999e+22\000\344@").should == obj
931963
end
964+
965+
it "raises ArgumentError when end of byte sequence reached before float string representation end" do
966+
Marshal.dump(1.3).should == "\x04\bf\b1.3"
967+
968+
-> {
969+
Marshal.send(@method, "\004\bf\v1")
970+
}.should raise_error(ArgumentError, "marshal data too short")
971+
end
932972
end
933973

934974
describe "for an Integer" do
@@ -1114,6 +1154,14 @@ def io.binmode; raise "binmode"; end
11141154
it "raises ArgumentError if given a nonexistent class" do
11151155
-> { Marshal.send(@method, "\x04\bc\vStrung") }.should raise_error(ArgumentError)
11161156
end
1157+
1158+
it "raises ArgumentError when end of byte sequence reached before class name end" do
1159+
Marshal.dump(String).should == "\x04\bc\vString"
1160+
1161+
-> {
1162+
Marshal.send(@method, "\x04\bc\vStr")
1163+
}.should raise_error(ArgumentError, "marshal data too short")
1164+
end
11171165
end
11181166

11191167
describe "for a Module" do
@@ -1128,6 +1176,14 @@ def io.binmode; raise "binmode"; end
11281176
it "loads an old module" do
11291177
Marshal.send(@method, "\x04\bM\vKernel").should == Kernel
11301178
end
1179+
1180+
it "raises ArgumentError when end of byte sequence reached before module name end" do
1181+
Marshal.dump(Kernel).should == "\x04\bm\vKernel"
1182+
1183+
-> {
1184+
Marshal.send(@method, "\x04\bm\vKer")
1185+
}.should raise_error(ArgumentError, "marshal data too short")
1186+
end
11311187
end
11321188

11331189
describe "for a wrapped C pointer" do

src/main/ruby/truffleruby/core/marshal.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,8 +1387,10 @@ def construct_range(klass)
13871387
end
13881388

13891389
class IOState < State
1390-
def consume(bytes)
1391-
@stream.read(bytes)
1390+
def consume(bytes_count)
1391+
string = @stream.read(bytes_count)
1392+
raise ArgumentError, 'marshal data too short' if string.bytesize < bytes_count
1393+
string
13921394
end
13931395

13941396
def consume_byte
@@ -1411,11 +1413,11 @@ def inspect
14111413
"#<Marshal::StringState #{@stream[@consumed..-1].inspect}>"
14121414
end
14131415

1414-
def consume(bytes)
1415-
raise ArgumentError, 'marshal data too short' if @consumed > @stream.bytesize
1416-
data = @stream.byteslice @consumed, bytes
1417-
@consumed += bytes
1418-
data
1416+
def consume(bytes_count)
1417+
raise ArgumentError, 'marshal data too short' if @consumed + bytes_count > @stream.bytesize
1418+
string = @stream.byteslice @consumed, bytes_count
1419+
@consumed += bytes_count
1420+
string
14191421
end
14201422

14211423
def consume_byte

0 commit comments

Comments
 (0)