From 8527e579e9766c985ff921aaad8e299fa5e8fc96 Mon Sep 17 00:00:00 2001 From: Shizuo Fujita Date: Wed, 1 Jul 2026 10:52:45 +0900 Subject: [PATCH] Validate String argument in Zstd.read_skippable_frame rb_read_skippable_frame called RSTRING_PTR/RSTRING_LEN on the argument without going through StringValue(), unlike every other public API. Passing a non-String reinterpreted the object as an RString and dereferenced attacker-influenced fields, causing a SEGV (e.g. a Fixnum argument crashed at an address derived from its immediate value). Add StringValue() so non-String arguments raise TypeError while String-convertible objects (#to_str) keep working. Covered by specs. Co-Authored-By: Claude Opus 4.8 (1M context) --- ext/zstdruby/skippable_frame.c | 1 + spec/zstd-skippable_frame_spec.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/ext/zstdruby/skippable_frame.c b/ext/zstdruby/skippable_frame.c index 73534b8..c22be85 100644 --- a/ext/zstdruby/skippable_frame.c +++ b/ext/zstdruby/skippable_frame.c @@ -36,6 +36,7 @@ static VALUE rb_write_skippable_frame(int argc, VALUE *argv, VALUE self) static VALUE rb_read_skippable_frame(VALUE self, VALUE input_value) { + StringValue(input_value); char* input_data = RSTRING_PTR(input_value); size_t input_size = RSTRING_LEN(input_value); diff --git a/spec/zstd-skippable_frame_spec.rb b/spec/zstd-skippable_frame_spec.rb index c5c882e..7d1c8c9 100644 --- a/spec/zstd-skippable_frame_spec.rb +++ b/spec/zstd-skippable_frame_spec.rb @@ -30,5 +30,22 @@ end end + context 'non-String argument' do + [nil, 123456789, :symbol, [1, 2, 3], Object.new].each do |arg| + it "raises TypeError for #{arg.class}" do + expect { Zstd.read_skippable_frame(arg) }.to raise_error(TypeError) + end + end + end + + context 'String-convertible argument' do + it 'accepts objects responding to #to_str' do + compressed_data = Zstd.compress(SecureRandom.hex(150)) + frame = Zstd.write_skippable_frame(compressed_data, "sample data") + convertible = Object.new + convertible.define_singleton_method(:to_str) { frame } + expect(Zstd.read_skippable_frame(convertible)).to eq "sample data" + end + end end end