Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions ext/msgpack/unpacker.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ static inline void _msgpack_unpacker_free_stack(msgpack_unpacker_stack_t* stack)
}
stack->data = NULL;
stack->depth = 0;
stack->floor = 0;
}
}

Expand Down Expand Up @@ -169,7 +170,7 @@ void _msgpack_unpacker_reset(msgpack_unpacker_t* uk)
uk->head_byte = HEAD_BYTE_REQUIRED;

/*memset(uk->stack, 0, sizeof(msgpack_unpacker_t) * uk->stack.depth);*/
uk->stack.depth = 0;
msgpack_unpacker_stack_rewind(uk);
uk->last_object = Qnil;
uk->reading_raw = Qnil;
uk->reading_raw_remaining = 0;
Expand Down Expand Up @@ -387,13 +388,14 @@ static inline int read_raw_body_begin(msgpack_unpacker_t* uk, int raw_type)
return PRIMITIVE_STACK_TOO_DEEP;
}
size_t barrier_depth = uk->stack.depth;
size_t saved_floor = uk->stack.floor;
int raised;

uk->stack.floor = barrier_depth;
obj = protected_proc_call(proc, 1, &uk->self, &raised);
uk->stack.floor = saved_floor;

/* The user proc can drive the unpacker itself (Unpacker#read, #skip,
* or a rescued error) and leave stack.depth anywhere, including 0.
* Restore it to just below the barrier we pushed instead of an
* unconditional decrement, which would underflow to SIZE_MAX. */
/* Not --depth: the proc may return with entries left above the barrier. */
uk->stack.depth = barrier_depth - 1;

if (raised) {
Expand Down Expand Up @@ -892,6 +894,10 @@ int msgpack_unpacker_skip(msgpack_unpacker_t* uk, size_t target_stack_depth)
container_completed:
{
msgpack_unpacker_stack_entry_t* top = _msgpack_unpacker_stack_entry_top(uk);
if(top->type == STACK_TYPE_RECURSIVE) {
STACK_FREE(uk);
return PRIMITIVE_OBJECT_COMPLETE;
}

/* this section optimized out */
// TODO object_complete still creates objects which should be optimized out
Expand Down
6 changes: 6 additions & 0 deletions ext/msgpack/unpacker.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef struct {

struct msgpack_unpacker_stack_t {
size_t depth;
size_t floor;
size_t capacity;
msgpack_unpacker_stack_entry_t *data;
};
Expand Down Expand Up @@ -109,6 +110,11 @@ static inline void msgpack_unpacker_set_allow_unknown_ext(msgpack_unpacker_t* uk
uk->allow_unknown_ext = enable;
}

static inline void msgpack_unpacker_stack_rewind(msgpack_unpacker_t* uk)
{
uk->stack.depth = uk->stack.floor;
}


/* error codes */
#define PRIMITIVE_CONTAINER_START 1
Expand Down
2 changes: 1 addition & 1 deletion ext/msgpack/unpacker_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ static VALUE Unpacker_allow_unknown_ext_p(VALUE self)

NORETURN(static void raise_unpacker_error(msgpack_unpacker_t *uk, int r))
{
uk->stack.depth = 0;
msgpack_unpacker_stack_rewind(uk);
switch(r) {
case PRIMITIVE_EOF:
rb_raise(rb_eEOFError, "end of buffer reached");
Expand Down
55 changes: 50 additions & 5 deletions spec/factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,10 @@ class << Symbol
end

it 'does not corrupt the stack when a recursive unpacker leaves the depth at zero' do
# A recursive proc that rescues an inner read error, or that calls #skip,
# can drive stack.depth down to 0 before read_raw_body_begin pops its
# barrier. The unconditional pop then underflowed depth to SIZE_MAX and
# read/wrote out-of-bounds stack entries (SIGSEGV). The payloads leave no
# trailing bytes, so a fixed unpacker returns without raising.
# A recursive proc that rescued an inner read error, or that called #skip,
# used to make read_raw_body_begin underflow stack.depth to SIZE_MAX when
# it popped its barrier (SIGSEGV). The payloads leave no trailing bytes,
# so a fixed unpacker returns without raising.
skip if IS_JRUBY

rescuing = MessagePack::Factory.new
Expand Down Expand Up @@ -747,6 +746,52 @@ class << Symbol
expect(rescuing.unpack(MessagePack.pack([1, 2, 3]))).to eq([1, 2, 3])
expect(skipping.unpack(MessagePack.pack("ok"))).to eq("ok")
end

it 'keeps outer containers GC-marked while a recursive unpacker rescues, skips or resets' do
skip if IS_JRUBY

factory = MessagePack::Factory.new
factory.register_type(0x01, Class.new,
packer: ->(_obj, packer) { packer.write(nil) },
unpacker: ->(u) {
begin
u.read
rescue MessagePack::MalformedFormatError
end
[:rescued]
},
recursive: true,
)
factory.register_type(0x02, Class.new,
packer: ->(_obj, packer) { packer.write(nil) },
unpacker: ->(u) { u.skip; [:skipped] },
recursive: true,
)
factory.register_type(0x03, Class.new,
packer: ->(_obj, packer) { packer.write(nil) },
unpacker: ->(u) { u.reset; [:reset] },
recursive: true,
)

payloads = ["\x92\xd4\x01\xc1\x2b".b, "\x92\xd4\x02\x2a\x2b".b, "\x91\xd4\x03".b]
results = []
begin
GC.stress = true
10.times do
payloads.each do |bytes|
results << begin
factory.unpack(bytes)
rescue => e
e
end
end
end
ensure
GC.stress = false
end

expect(results).to eq([[[:rescued], 43], [[:skipped], 43], [[:reset]]] * 10)
end
end

describe 'memsize' do
Expand Down
Loading