brianmario / yajl-ruby

A streaming JSON parsing and encoding library for Ruby (C bindings to yajl)
http://rdoc.info/projects/brianmario/yajl-ruby
MIT License
1.48k stars 169 forks source link

Stuck in infinite loop #205

Closed drbugfinder-work closed 2 years ago

drbugfinder-work commented 2 years ago

In context of fluentd/td-agent, we ran into an issue, where yajl is running into an infinite loop.

In gdb we could find out, that buf->len is 0 at that time, so need is 0, as well. https://github.com/brianmario/yajl-ruby/blob/7168bd79b888900aa94523301126f968a93eb3a6/ext/yajl/yajl_buf.c#L62 The method is called with the variable want=0, so the shift loop will never exit. https://github.com/brianmario/yajl-ruby/blob/7168bd79b888900aa94523301126f968a93eb3a6/ext/yajl/yajl_buf.c#L64

After digging a little bit deeper, we could see, that the malloc functions are completely missing error handling https://github.com/brianmario/yajl-ruby/blob/7168bd79b888900aa94523301126f968a93eb3a6/ext/yajl/yajl_alloc.c#L44 In our case buf->len was 0, so we assume, that the malloc call failed.

ppisar commented 2 years ago

I worry the patch does not fix the integer overflow. The code still contains:

while (want >= (need - buf->used)) need <<= 1;

All the variables are unsigned int. If want and need are UINT_MAX and bug->used is 0, then it will loop indefinitely. Basically if want is UINT_MAX, then the while condition will be true all the time.

But checking want for UINT_MAX is not enough. You will get the same loop any time want >= need, provided need overflows at least once. At each overflow need becomes smaller and smaller until it becomes 0. Then the loop will never terminate because need will be costant 0 and not state changes at any subsequent iteration.

ppisar commented 2 years ago

A note regarding correctness of the patchset: It traded a memory corruption for a data corruption. Now if yajl_buf_ensure_available() cannot fulfill the allocation, it returns an error. yajl_buf_ensure_available() is called only from yajl_buf_append() whose job is to append a data into a buffer. Now on yajl_buf_ensure_available() yajl_buf_append() silently discards the request:

void yajl_buf_append(yajl_buf buf, const void * data, unsigned int len)
{
    yajl_buf_ensure_available(buf, len);
    if (yajl_buf_ensure_available(buf, len)) {
        return;
    }
    [...]
}

Hence an affected application won't crash and won't loop, but it will truncate data. A correct fix should keep propagating the error back to the application. If it is too difficult to fix it (and I guess it is because yajl cascades many calls without any mechanism for reporting errors), the bundled yajl should rather abort the process.

jhawthorn commented 2 years ago

@ppisar can you provide a failing test case, I believe the error cascading works correctly and results in an error being returned to the application. The error is recorded on the buffer and attempts to use the data in the buffer will return an error.

https://github.com/brianmario/yajl-ruby/blob/15093d3f6ed1250a8965ec60a6b633c8792cb2f4/ext/yajl/yajl_gen.c#L365-L368

/home/jhawthorn/src/yajl-ruby/lib/yajl.rb:80:in `encode': YAJL internal error: failed to allocate memory (Yajl::EncodeError)
    from /home/jhawthorn/src/yajl-ruby/lib/yajl.rb:80:in `encode'
    from test.rb:6:in `block in <main>'
    from test.rb:5:in `loop'
    from test.rb:5:in `<main>'
ppisar commented 2 years ago

I see. I overlooked you are checking the failure later. In that case the error seems indeed propagated. I'm sorry for disturbing you.

yvvdwf commented 2 years ago

Hello,

I saw CVE-2022-24795 in release notes of GHE v3.4.1, then I'm here after searching a little bit about the CVE. I have had the same remark as ppisar about the infinite loop. Should I provide a test case for it here or send you via other private channel?

Best regards,