<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/style.xsl" type="text/xsl"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Most recent entries from all</title>
    <link>https://vulnerability.circl.lu</link>
    <description>Contains only the most 10 recent entries.</description>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <language>en</language>
    <lastBuildDate>Wed, 30 Sep 2026 17:50:03 +0000</lastBuildDate>
    <item>
      <title>GHSA-x2f5-4prf-w687 — Ruby json: JSON generator heap buffer overflow when streaming to an IO</title>
      <link>https://vulnerability.circl.lu/vuln/ghsa-x2f5-4prf-w687</link>
      <description>&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; RubyGems: json&lt;/p&gt;
&lt;p&gt;### Summary&lt;/p&gt;
&lt;p&gt;`JSON.dump(obj, io)` and `JSON::State#generate(obj, io)` can write past the
internal JSON generator buffer when a streamed object contains an
attacker-controlled string near 16 KB. The issue is a heap out-of-bounds write
in the IO-streaming path and is demonstrated as a reliable process crash /
denial of service.&lt;/p&gt;
&lt;p&gt;This was triaged on HackerOne as report #3785370. The issue was confirmed there
and I was asked to open it here.&lt;/p&gt;
&lt;p&gt;### Details&lt;/p&gt;
&lt;p&gt;Root cause is in `ext/json/fbuffer/fbuffer.h`, `fbuffer_do_inc_capa()`.&lt;/p&gt;
&lt;p&gt;On the IO path, the buffer is grown to `FBUFFER_IO_BUFFER_SIZE` (16383), but the
early return checks total capacity instead of remaining capacity:&lt;/p&gt;
&lt;p&gt;```c
if (RB_UNLIKELY(fb-&amp;gt;io)) {
    if (fb-&amp;gt;capa &amp;lt; FBUFFER_IO_BUFFER_SIZE) {
        fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
    } else {
        fbuffer_flush(fb);
    }&lt;/p&gt;
&lt;p&gt;if (RB_LIKELY(requested &amp;lt; fb-&amp;gt;capa)) {
        return;
    }
}
```&lt;/p&gt;
&lt;p&gt;If `fb-&amp;gt;len` already contains JSON syntax bytes, and a string flush has
`16383 - fb-&amp;gt;len &amp;lt;= requested &amp;lt; 16383`, this check returns even though there is
not enough space left. `fbuffer_append_reserved()` then writes past the buffer:&lt;/p&gt;
&lt;p&gt;```c
MEMCPY(fb-&amp;gt;ptr + fb-&amp;gt;len, newstr, char, len);
```&lt;/p&gt;
&lt;p&gt;The minimal fix is to compare against the remaining capacity:&lt;/p&gt;
&lt;p&gt;```diff
-        if (RB_LIKELY(requested &amp;lt; fb-&amp;gt;capa)) {
+        if (RB_LIKELY(requested &amp;lt;= fb-&amp;gt;capa - fb-&amp;gt;len)) {
             return;
         }
```&lt;/p&gt;
&lt;p&gt;### PoC&lt;/p&gt;
&lt;p&gt;```ruby
require &amp;#34;json&amp;#34;
require &amp;#34;stringio&amp;#34;&lt;/p&gt;
&lt;p&gt;io = StringIO.new…&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; RubyGems: json&lt;/p&gt;
&lt;p&gt;### Summary&lt;/p&gt;
&lt;p&gt;`JSON.dump(obj, io)` and `JSON::State#generate(obj, io)` can write past the
internal JSON generator buffer when a streamed object contains an
attacker-controlled string near 16 KB. The issue is a heap out-of-bounds write
in the IO-streaming path and is demonstrated as a reliable process crash /
denial of service.&lt;/p&gt;
&lt;p&gt;This was triaged on HackerOne as report #3785370. The issue was confirmed there
and I was asked to open it here.&lt;/p&gt;
&lt;p&gt;### Details&lt;/p&gt;
&lt;p&gt;Root cause is in `ext/json/fbuffer/fbuffer.h`, `fbuffer_do_inc_capa()`.&lt;/p&gt;
&lt;p&gt;On the IO path, the buffer is grown to `FBUFFER_IO_BUFFER_SIZE` (16383), but the
early return checks total capacity instead of remaining capacity:&lt;/p&gt;
&lt;p&gt;```c
if (RB_UNLIKELY(fb-&amp;gt;io)) {
    if (fb-&amp;gt;capa &amp;lt; FBUFFER_IO_BUFFER_SIZE) {
        fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
    } else {
        fbuffer_flush(fb);
    }&lt;/p&gt;
&lt;p&gt;if (RB_LIKELY(requested &amp;lt; fb-&amp;gt;capa)) {
        return;
    }
}
```&lt;/p&gt;
&lt;p&gt;If `fb-&amp;gt;len` already contains JSON syntax bytes, and a string flush has
`16383 - fb-&amp;gt;len &amp;lt;= requested &amp;lt; 16383`, this check returns even though there is
not enough space left. `fbuffer_append_reserved()` then writes past the buffer:&lt;/p&gt;
&lt;p&gt;```c
MEMCPY(fb-&amp;gt;ptr + fb-&amp;gt;len, newstr, char, len);
```&lt;/p&gt;
&lt;p&gt;The minimal fix is to compare against the remaining capacity:&lt;/p&gt;
&lt;p&gt;```diff
-        if (RB_LIKELY(requested &amp;lt; fb-&amp;gt;capa)) {
+        if (RB_LIKELY(requested &amp;lt;= fb-&amp;gt;capa - fb-&amp;gt;len)) {
             return;
         }
```&lt;/p&gt;
&lt;p&gt;### PoC&lt;/p&gt;
&lt;p&gt;```ruby
require &amp;#34;json&amp;#34;
require &amp;#34;stringio&amp;#34;&lt;/p&gt;
&lt;p&gt;io = StringIO.new…&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/ghsa-x2f5-4prf-w687</guid>
    </item>
  </channel>
</rss>
