@@ -699,6 +699,48 @@ class << Symbol
699699 # recursed unbounded in C and crashed the VM (SIGSEGV) rather than raising.
700700 expect { factory . load ( payload ) } . to raise_error ( MessagePack ::StackError )
701701 end
702+
703+ it 'does not corrupt the stack when a recursive unpacker leaves the depth at zero' do
704+ # A recursive proc that rescues an inner read error, or that calls #skip,
705+ # can drive stack.depth down to 0 before read_raw_body_begin pops its
706+ # barrier. The unconditional pop then underflowed depth to SIZE_MAX and
707+ # read/wrote out-of-bounds stack entries (SIGSEGV). The payloads leave no
708+ # trailing bytes, so a fixed unpacker returns without raising.
709+ skip if IS_JRUBY
710+
711+ rescuing = MessagePack ::Factory . new
712+ rescuing . register_type ( 0x01 , Class . new ,
713+ packer : -> ( _obj , packer ) { packer . write ( nil ) } ,
714+ unpacker : -> ( u ) {
715+ begin
716+ u . read
717+ rescue MessagePack ::MalformedFormatError , EOFError
718+ nil
719+ end
720+ } ,
721+ recursive : true ,
722+ )
723+
724+ skipping = MessagePack ::Factory . new
725+ skipping . register_type ( 0x02 , Class . new ,
726+ packer : -> ( _obj , packer ) { packer . write ( nil ) } ,
727+ unpacker : -> ( u ) { u . skip } ,
728+ recursive : true ,
729+ )
730+
731+ payloads = [
732+ [ rescuing , "\xd4 \x01 \xc1 " . b ] , # fixext1 type=1, then an invalid byte
733+ [ rescuing , "\xd4 \x01 " . b ] , # fixext1 type=1, then truncated (EOF)
734+ [ skipping , "\xd4 \x02 \x91 \x2a " . b ] , # fixext1 type=2, fixarray(1), 42
735+ ]
736+ payloads . each do |factory , bytes |
737+ 100 . times { factory . unpack ( bytes ) }
738+ end
739+
740+ # The stack is intact: a fresh unpack still decodes correctly.
741+ expect ( rescuing . unpack ( MessagePack . pack ( [ 1 , 2 , 3 ] ) ) ) . to eq ( [ 1 , 2 , 3 ] )
742+ expect ( skipping . unpack ( MessagePack . pack ( "ok" ) ) ) . to eq ( "ok" )
743+ end
702744 end
703745
704746 describe 'memsize' do
0 commit comments