Zion Boggan
repos/Security Portfolio/security-research-notebook/dragonfly-stream-restore-oom/index.html
zionboggan.com ↗
324 lines · html
History for this file →
1
<!doctype html>
2
<html lang="en"><head><meta charset="utf-8">
3
<meta name="viewport" content="width=device-width, initial-scale=1.0">
4
<title>Authenticated DoS: Dragonfly Server Crash via Crafted Stream RESTORE Payload | Zion Boggan</title>
5
<meta name="description" content="Unbounded allocation in Dragonfly&amp;#x27;s stream RESTORE path.">
6
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%230c0e12'/%3E%3Ctext x='16' y='22' font-family='monospace' font-size='15' fill='%236cc7b8' text-anchor='middle'%3Ezb%3C/text%3E%3C/svg%3E">
7
<style>
8
  :root{
9
    --bg:#0c0e12; --bg2:#0f1217; --panel:#14181f; --panel2:#171c24;
10
    --line:#222936; --line2:#2c3543;
11
    --ink:#e8eaed; --soft:#c3cad4; --muted:#8a94a3; --faint:#5d6675;
12
    --accent:#6cc7b8; --accent-dim:#274b47;
13
    --maxw:1020px;
14
  }
15
  *{box-sizing:border-box;}
16
  html{scroll-behavior:smooth;}
17
  body{margin:0;background:var(--bg);color:var(--ink);
18
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
19
    font-size:16px;line-height:1.65;-webkit-font-smoothing:antialiased;}
20
  .mono{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;}
21
  a{color:var(--accent);text-decoration:none;}
22
  a:hover{color:#8fe0d2;}
23
  .wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px;}
24
 
25
  /* nav */
26
  nav{position:sticky;top:0;z-index:20;background:rgba(12,14,18,.82);
27
    backdrop-filter:blur(10px);border-bottom:1px solid var(--line);}
28
  nav .wrap{display:flex;align-items:center;justify-content:space-between;height:58px;}
29
  nav .brand{font-weight:600;letter-spacing:.2px;}
30
  nav .brand .dot{color:var(--accent);}
31
  nav .links{display:flex;gap:26px;font-size:13.5px;}
32
  nav .links a{color:var(--muted);}
33
  nav .links a:hover{color:var(--ink);}
34
  @media(max-width:680px){nav .links{display:none;}}
35
 
36
  /* hero */
37
  header.hero{padding:74px 0 54px;border-bottom:1px solid var(--line);
38
    background:radial-gradient(900px 380px at 78% -10%, #11201e 0%, transparent 60%);}
39
  .avail{font-size:12.5px;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent);
40
    display:flex;align-items:center;gap:9px;margin-bottom:20px;}
41
  .avail .pulse{width:7px;height:7px;border-radius:50%;background:var(--accent);
42
    box-shadow:0 0 0 0 rgba(108,199,184,.5);animation:p 2.4s infinite;}
43
  @keyframes p{0%{box-shadow:0 0 0 0 rgba(108,199,184,.45)}70%{box-shadow:0 0 0 8px rgba(108,199,184,0)}100%{box-shadow:0 0 0 0 rgba(108,199,184,0)}}
44
  h1{font-size:clamp(34px,6vw,52px);line-height:1.05;margin:0 0 8px;letter-spacing:-1px;font-weight:680;}
45
  .hero .sub{font-size:clamp(16px,2.4vw,20px);color:var(--soft);margin:0 0 24px;font-weight:500;}
46
  .hero .lede{max-width:660px;color:var(--soft);font-size:17px;margin:0 0 28px;}
47
  .hero .lede b{color:var(--ink);font-weight:600;}
48
  .cta{display:flex;flex-wrap:wrap;gap:12px;align-items:center;}
49
  .btn{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:8px;
50
    font-size:14.5px;font-weight:550;border:1px solid var(--line2);color:var(--ink);background:var(--panel);}
51
  .btn:hover{border-color:var(--accent-dim);background:var(--panel2);color:var(--ink);}
52
  .btn.primary{background:var(--accent);color:#06231f;border-color:var(--accent);font-weight:650;}
53
  .btn.primary:hover{background:#8fe0d2;color:#06231f;}
54
  .meta{margin-top:26px;display:flex;flex-wrap:wrap;gap:8px 22px;font-size:13px;color:var(--muted);}
55
  .meta .mono{color:var(--faint);}
56
 
57
  /* sections */
58
  section{padding:64px 0;border-bottom:1px solid var(--line);}
59
  .shead{display:flex;align-items:baseline;gap:14px;margin-bottom:30px;}
60
  .shead .idx{font-size:13px;color:var(--accent);letter-spacing:1px;}
61
  .shead h2{font-size:14px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:0;font-weight:600;}
62
  .shead .rule{flex:1;height:1px;background:var(--line);}
63
 
64
  /* flagship */
65
  .flag{background:linear-gradient(180deg,var(--panel) 0%,var(--bg2) 100%);
66
    border:1px solid var(--line2);border-radius:14px;overflow:hidden;}
67
  .flag .top{padding:30px 32px 8px;}
68
  .flag .tag{font-size:12px;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent);margin-bottom:12px;}
69
  .flag h3{font-size:27px;margin:0 0 6px;letter-spacing:-.4px;}
70
  .flag h3 .v{font-size:13px;color:var(--muted);font-weight:500;margin-left:8px;letter-spacing:0;}
71
  .flag .grid{display:grid;grid-template-columns:1.25fr 1fr;gap:30px;padding:14px 32px 30px;}
72
  .flag p{color:var(--soft);margin:0 0 16px;}
73
  .flag .stats{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:6px;}
74
  .stat{background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:13px 15px;}
75
  .stat .n{font-size:21px;font-weight:680;color:var(--ink);}
76
  .stat .k{font-size:12px;color:var(--muted);margin-top:2px;}
77
  .spec{background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:18px 18px;}
78
  .spec .sk{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);margin-bottom:10px;}
79
  .spec ul{margin:0;padding:0;list-style:none;font-size:13.5px;}
80
  .spec li{padding:6px 0;border-top:1px solid var(--line);color:var(--soft);display:flex;justify-content:space-between;gap:14px;}
81
  .spec li:first-child{border-top:none;}
82
  .spec li span{color:var(--muted);}
83
  .flag .foot{padding:0 32px 28px;display:flex;gap:18px;flex-wrap:wrap;font-size:14px;}
84
  @media(max-width:720px){.flag .grid{grid-template-columns:1fr;}}
85
 
86
  /* lab cards */
87
  .cards{display:grid;grid-template-columns:1fr 1fr;gap:20px;}
88
  @media(max-width:680px){.cards{grid-template-columns:1fr;}}
89
  .card{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--panel);
90
    display:flex;flex-direction:column;transition:border-color .15s,transform .15s;}
91
  .card:hover{border-color:var(--accent-dim);transform:translateY(-2px);}
92
  .card .thumb{height:172px;overflow:hidden;border-bottom:1px solid var(--line);background:#fff;}
93
  .card .thumb img{width:100%;height:100%;object-fit:cover;object-position:top left;display:block;}
94
  .card .body{padding:18px 20px 20px;display:flex;flex-direction:column;flex:1;}
95
  .card h3{margin:0 0 9px;font-size:17px;}
96
  .card p{margin:0 0 14px;font-size:14px;color:var(--soft);flex:1;}
97
  .tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px;}
98
  .tags span{font-size:11.5px;color:var(--muted);background:var(--bg);border:1px solid var(--line);
99
    border-radius:5px;padding:3px 8px;}
100
  .card .lnk{font-size:13.5px;font-family:ui-monospace,Menlo,monospace;}
101
  .card .lnk::after{content:" →";}
102
 
103
  /* research */
104
  .rlede{color:var(--soft);max-width:680px;margin:-6px 0 26px;}
105
  .research{display:flex;flex-direction:column;gap:0;border:1px solid var(--line);border-radius:12px;overflow:hidden;}
106
  .ritem{display:grid;grid-template-columns:120px 1fr auto;gap:18px;align-items:center;
107
    padding:18px 22px;border-top:1px solid var(--line);}
108
  .ritem:first-child{border-top:none;}
109
  .ritem:hover{background:var(--panel);}
110
  .ritem .cls{font-size:11px;letter-spacing:.5px;text-transform:uppercase;color:var(--accent);}
111
  .ritem h3{margin:0 0 3px;font-size:16px;}
112
  .ritem p{margin:0;font-size:13.5px;color:var(--muted);}
113
  .ritem .go{font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:nowrap;}
114
  @media(max-width:680px){.ritem{grid-template-columns:1fr;gap:6px;}.ritem .go{margin-top:4px;}}
115
  .progs{margin-top:22px;}
116
  .progs .sk{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);margin-bottom:11px;}
117
  .progs .row{display:flex;flex-wrap:wrap;gap:7px;}
118
  .progs .row span{font-size:12.5px;color:var(--soft);background:var(--panel);border:1px solid var(--line);
119
    border-radius:6px;padding:4px 10px;}
120
 
121
  /* credentials */
122
  .cred{display:grid;grid-template-columns:1.1fr 1fr;gap:28px;}
123
  @media(max-width:680px){.cred{grid-template-columns:1fr;}}
124
  .cred p{color:var(--soft);margin:0 0 14px;}
125
  .cred .role{font-size:14px;color:var(--muted);}
126
  .cred .role b{color:var(--ink);font-weight:600;}
127
  .certs{list-style:none;margin:0;padding:0;}
128
  .certs li{padding:9px 0;border-top:1px solid var(--line);font-size:14px;color:var(--soft);
129
    display:flex;gap:10px;align-items:baseline;}
130
  .certs li:first-child{border-top:none;}
131
  .certs li .c{color:var(--accent);font-family:ui-monospace,Menlo,monospace;font-size:12px;}
132
 
133
  footer{padding:46px 0 64px;}
134
  footer .row{display:flex;flex-wrap:wrap;justify-content:space-between;gap:18px;align-items:center;}
135
  footer .links a{color:var(--soft);margin-right:20px;font-size:14px;}
136
  footer .note{color:var(--faint);font-size:12.5px;max-width:520px;}
137
 
138
  .detail-hero{padding:40px 0 26px;}
139
  .back{display:inline-block;font-size:13px;color:var(--muted);margin-bottom:20px;font-family:ui-monospace,Menlo,monospace;}
140
  .back:hover{color:var(--ink);}
141
  .kicker{font-size:12px;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin-bottom:13px;font-family:ui-monospace,Menlo,monospace;}
142
  .detail-hero h1{font-size:clamp(26px,4.6vw,38px);margin:0 0 12px;letter-spacing:-.5px;}
143
  .detail-hero .tagline{font-size:clamp(15px,2vw,18px);color:var(--soft);max-width:800px;margin:0 0 16px;}
144
  .facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-top:22px;}
145
  .content{padding:8px 0 0;max-width:840px;}
146
  .content h1{font-size:24px;margin:40px 0 14px;letter-spacing:-.4px;color:var(--ink);}
147
  .content h2{font-size:13px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:42px 0 15px;font-weight:600;border-top:1px solid var(--line);padding-top:28px;}
148
  .content h3{font-size:17px;margin:28px 0 10px;color:var(--ink);font-weight:600;}
149
  .content h4{font-size:14px;margin:22px 0 8px;color:var(--soft);font-weight:600;text-transform:uppercase;letter-spacing:.5px;}
150
  .content p{color:var(--soft);margin:0 0 15px;}
151
  .content ul,.content ol{color:var(--soft);margin:0 0 15px;padding-left:22px;}
152
  .content li{margin:5px 0;}
153
  .content strong{color:var(--ink);font-weight:600;}
154
  .content a{color:var(--accent);}
155
  .content code{font-family:ui-monospace,Menlo,monospace;font-size:12.8px;background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:1px 5px;color:var(--soft);}
156
  .content pre{background:var(--bg2);border:1px solid var(--line2);border-radius:10px;padding:15px 18px;overflow-x:auto;margin:0 0 18px;}
157
  .content pre code{background:none;border:none;padding:0;font-size:12.4px;color:var(--soft);line-height:1.6;white-space:pre;}
158
  .content table{width:100%;border-collapse:collapse;margin:2px 0 20px;font-size:13.3px;}
159
  .content th{text-align:left;color:var(--muted);font-weight:600;border-bottom:1px solid var(--line2);padding:9px 12px;font-size:11px;letter-spacing:.6px;text-transform:uppercase;}
160
  .content td{color:var(--soft);border-bottom:1px solid var(--line);padding:9px 12px;vertical-align:top;}
161
  .content blockquote{border-left:3px solid var(--accent-dim);margin:0 0 16px;padding:2px 0 2px 18px;color:var(--muted);}
162
  .content hr{border:none;border-top:1px solid var(--line);margin:30px 0;}
163
  /* notebook index */
164
  .nbgroup{margin:40px 0 0;}
165
  .nbgroup h2{font-size:13px;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin:0 0 4px;font-weight:600;}
166
  .nbgroup .gd{color:var(--faint);font-size:13px;margin:0 0 14px;}
167
  .nbtable{width:100%;border-collapse:collapse;font-size:14px;border:1px solid var(--line);border-radius:12px;overflow:hidden;}
168
  .nbtable tr{border-top:1px solid var(--line);}
169
  .nbtable tr:first-child{border-top:none;}
170
  .nbtable tr:hover{background:var(--panel);}
171
  .nbtable td{padding:14px 16px;vertical-align:top;}
172
  .nbtable .cls{white-space:nowrap;color:var(--accent);font-family:ui-monospace,Menlo,monospace;font-size:11.5px;text-transform:uppercase;letter-spacing:.5px;width:150px;}
173
  .nbtable .ti a{font-weight:600;color:var(--ink);}
174
  .nbtable .ti a:hover{color:var(--accent);}
175
  .nbtable .ol{color:var(--muted);font-size:13px;margin-top:3px;}
176
  @media(max-width:680px){.nbtable .cls{width:auto;display:block;}}
177
</style>
178
<link rel="canonical" href="https://zionboggan.com/security-research-notebook/dragonfly-stream-restore-oom/">
179
<meta name="author" content="Zion Boggan">
180
<meta name="robots" content="index, follow, max-image-preview:large">
181
<meta property="og:type" content="article">
182
<meta property="og:site_name" content="Zion Boggan">
183
<meta property="og:title" content="Authenticated DoS: Dragonfly Server Crash via Crafted Stream RESTORE Payload | Zion Boggan">
184
<meta property="og:description" content="Unbounded allocation in Dragonfly&amp;#x27;s stream RESTORE path.">
185
<meta property="og:url" content="https://zionboggan.com/security-research-notebook/dragonfly-stream-restore-oom/">
186
<meta property="og:image" content="https://zionboggan.com/assets/og-default.png">
187
<meta name="twitter:card" content="summary_large_image">
188
<meta name="twitter:title" content="Authenticated DoS: Dragonfly Server Crash via Crafted Stream RESTORE Payload | Zion Boggan">
189
<meta name="twitter:description" content="Unbounded allocation in Dragonfly&amp;#x27;s stream RESTORE path.">
190
<meta name="twitter:image" content="https://zionboggan.com/assets/og-default.png">
191
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Authenticated DoS: Dragonfly Server Crash via Crafted Stream RESTORE Payload","description":"Unbounded allocation in Dragonfly&amp;#x27;s stream RESTORE path.","url":"https://zionboggan.com/security-research-notebook/dragonfly-stream-restore-oom/","image":"https://zionboggan.com/assets/og-default.png","author":{"@type":"Person","name":"Zion Boggan","url":"https://zionboggan.com"},"publisher":{"@type":"Person","name":"Zion Boggan"}}</script>
192
</head><body>
193
<nav><div class="wrap">
194
  <a class="brand mono" href="/" style="color:var(--ink)">zion_boggan<span class="dot">.</span></a>
195
  <span class="links"><a href="/#oversight">Oversight</a><a href="/#labs">Labs</a><a href="/#research">Research</a><a href="/security-research-notebook/">Notebook</a><a href="/">Home</a></span>
196
</div></nav>
197
<header class="hero detail-hero"><div class="wrap">
198
  <a class="back" href="/security-research-notebook/">&larr; Research notebook</a>
199
  <div class="kicker">DoS / OOM</div>
200
  <h1>Authenticated DoS: Dragonfly Server Crash via Crafted Stream RESTORE Payload</h1>
201
</div></header>
202
<section><div class="wrap"><div class="content">
203
<h2>Summary</h2>
204
<p>A vulnerability in Dragonfly&rsquo;s RDB deserialization allows an authenticated user to crash the server process by sending a single <code>RESTORE</code> command with a crafted stream payload. The vulnerability is caused by unbounded memory allocation in the stream consumer group deserialization path (<code>ReadStreams()</code> in <code>rdb_load.cc</code>), where attacker-controlled length values from the serialized payload are used directly in <code>std::vector::resize()</code> without any upper bound validation.</p>
205
<h2>Severity</h2>
206
<p><strong>P2, Server Availability</strong> (CVSS 7.5: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)</p>
207
<p>This is a denial-of-service vulnerability that crashes the entire Dragonfly server process, affecting all connected clients and all databases on the instance. On Aiven managed Dragonfly, every authenticated database user can trigger this.</p>
208
<h2>Vulnerability Details</h2>
209
<h3>Root Cause</h3>
210
<p>In <code>src/server/rdb_load.cc</code>, the <code>ReadStreams()</code> function deserializes stream data from RDB payloads (including <code>RESTORE</code> command input). Several length values are read from the attacker-controlled payload and used directly in memory allocation without upper bound checks:</p>
211
<p><strong>Location 1, Consumer group count (most direct)</strong>:</p>
212
<pre><code class="language-cpp">// rdb_load.cc, ReadStreams()
213
uint64_t cgroups_count;
214
SET_OR_UNEXPECT(LoadLen(nullptr), cgroups_count);
215
load_trace-&gt;stream_trace-&gt;cgroup.resize(cgroups_count);  // NO UPPER BOUND
216
</code></pre>
217
<p><strong>Location 2, PEL (Pending Entry List) size per group</strong>:</p>
218
<pre><code class="language-cpp">uint64_t pel_size;
219
SET_OR_UNEXPECT(LoadLen(nullptr), pel_size);
220
cgroup.pel_arr.resize(pel_size);  // NO UPPER BOUND
221
</code></pre>
222
<p><strong>Location 3, Consumer count per group</strong>:</p>
223
<pre><code class="language-cpp">uint64_t consumers_num;
224
SET_OR_UNEXPECT(LoadLen(nullptr), consumers_num);
225
cgroup.cons_arr.resize(consumers_num);  // NO UPPER BOUND
226
</code></pre>
227
<p><strong>Location 4, Per-consumer NACK array</strong>:</p>
228
<pre><code class="language-cpp">SET_OR_UNEXPECT(LoadLen(nullptr), pel_size);
229
consumer.nack_arr.resize(pel_size);  // NO UPPER BOUND
230
</code></pre>
231
<h3>Attack Path</h3>
232
<ol>
233
<li>Attacker authenticates to the Dragonfly instance (standard database credentials)</li>
234
<li>Attacker sends: <code>RESTORE key 0 &lt;crafted_payload&gt; REPLACE</code></li>
235
<li>The payload encodes <code>RDB_TYPE_STREAM_LISTPACKS_3</code> (type 21) with:, 1 valid stream node (passes initial validation), Valid stream metadata, Consumer groups count set to 1,073,741,824 (1 billion)</li>
236
<li><code>ReadStreams()</code> calls <code>cgroup.resize(1073741824)</code></li>
237
<li>Each <code>StreamCGTrace</code> is ~100 bytes → attempts to allocate ~100GB</li>
238
<li><code>std::vector::resize()</code> throws <code>std::bad_alloc</code></li>
239
<li>Exception is uncaught in the RESTORE command path → <code>std::terminate()</code> → process crash</li>
240
</ol>
241
<h3>Crash Mechanism</h3>
242
<p>The <code>RESTORE</code> command path (<code>GenericFamily::Restore()</code> → <code>OpRestore()</code> → <code>RdbRestoreValue::Add()</code> → <code>Parse()</code> → <code>ReadObj()</code> → <code>ReadStreams()</code>) does not wrap the deserialization in a try-catch block. When <code>std::bad_alloc</code> is thrown by the vector allocation, it propagates through the entire call chain and terminates the process.</p>
243
<h2>Proof of Concept</h2>
244
<h3>Environment</h3>
245
<ul>
246
<li>Dragonfly v1.37.2 (<code>docker.dragonflydb.io/dragonflydb/dragonfly:latest</code>)</li>
247
<li>Docker container on Linux host</li>
248
</ul>
249
<h3>Steps to Reproduce</h3>
250
<ol>
251
<li>Start Dragonfly:</li>
252
</ol>
253
<pre><code class="language-bash">docker run -d --name dragonfly-test -p 6379:6379 \
254
  docker.dragonflydb.io/dragonflydb/dragonfly:latest
255
</code></pre>
256
<ol start="2">
257
<li>Run the PoC script:</li>
258
</ol>
259
<pre><code class="language-bash">python3 poc_stream_oom.py --host 127.0.0.1 --port 6379
260
</code></pre>
261
<ol start="3">
262
<li>Observe: the Dragonfly process crashes and is no longer reachable.</li>
263
</ol>
264
<h3>PoC Script Output</h3>
265
<pre><code>[*] Building crafted stream RESTORE payload...
266
[*] Consumer group count: 1,073,741,824 (0x40000000)
267
[*] Payload size: 87 bytes
268
[+] Connected to server: df-v1.37.2
269
 
270
[!] Sending crafted RESTORE command...
271
[!] Key: __poc_crash_key__
272
[!] Expected result: server process crash (std::bad_alloc → terminate)
273
[?] Unexpected error: TimeoutError: Timeout reading from socket
274
 
275
[*] Checking if server is still alive...
276
[+] Server is NOT responding - crash confirmed!
277
</code></pre>
278
<h3>Impact Demonstration</h3>
279
<p>In our testing, the crafted payload with <code>cgroups_count = 4,294,967,296</code> (4 billion) not only crashed the Dragonfly process but triggered the Linux OOM killer, taking down the entire host system including SSH access. The host required several minutes to recover.</p>
280
<p><strong>On Aiven&rsquo;s managed infrastructure</strong>, this means a single authenticated database user could:
281
1. Crash their Dragonfly instance (immediate service disruption)
282
2. Potentially trigger OOM on shared infrastructure (affecting other tenants)
283
3. Repeat the attack on service restart for sustained DoS</p>
284
<h2>Affected Code</h2>
285
<ul>
286
<li><strong>File</strong>: <code>src/server/rdb_load.cc</code></li>
287
<li><strong>Function</strong>: <code>RdbLoaderBase::ReadStreams()</code></li>
288
<li><strong>Lines</strong>: Consumer group resize (~line 1690), PEL resize (~line 1710), consumer resize (~line 1720), NACK resize (~line 1740)</li>
289
<li><strong>Version</strong>: Confirmed on v1.37.2, likely affects all versions with stream support</li>
290
</ul>
291
<h2>Suggested Fix</h2>
292
<p>Add upper bound validation for all attacker-controlled length values before allocation:</p>
293
<pre><code class="language-cpp">// Before resize operations in ReadStreams:
294
constexpr uint64_t kMaxCGroups = 1 &lt;&lt; 20;      // 1M consumer groups
295
constexpr uint64_t kMaxPelSize = 1 &lt;&lt; 24;      // 16M PEL entries
296
constexpr uint64_t kMaxConsumers = 1 &lt;&lt; 20;    // 1M consumers
297
 
298
if (cgroups_count &gt; kMaxCGroups) {
299
    LOG(ERROR) &lt;&lt; &quot;Stream consumer group count too large: &quot; &lt;&lt; cgroups_count;
300
    return Unexpected(errc::rdb_file_corrupted);
301
}
302
load_trace-&gt;stream_trace-&gt;cgroup.resize(cgroups_count);
303
</code></pre>
304
<p>Additionally, consider wrapping the <code>RdbRestoreValue::Add()</code> path in a try-catch for <code>std::bad_alloc</code> to prevent any remaining unbounded allocation from crashing the process.</p>
305
<h2>References</h2>
306
<ul>
307
<li>Dragonfly source: https://github.com/dragonflydb/dragonfly</li>
308
<li>Similar class: CVE-2023-41056 (Redis RESTORE heap overflow), CVE-2023-41053 (Redis listpack integer overflow)</li>
309
<li>RDB format specification: https://rdb.fnordig.de/file_format.html</li>
310
</ul>
311
<h2>Proof of concept</h2>
312
<ul>
313
<li><a href="poc/dragonfly-stream-oom.py"><code>poc/dragonfly-stream-oom.py</code></a>, Stream RESTORE OOM crash.</li>
314
<li><a href="poc/dragonfly-cms-oom.py"><code>poc/dragonfly-cms-oom.py</code></a>, Count-Min-Sketch RESTORE OOM (related family).</li>
315
</ul>
316
<pre><code class="language-bash">python3 poc/dragonfly-stream-oom.py &lt;host&gt; &lt;port&gt; &lt;password&gt;
317
</code></pre>
318
<hr><p style="color:var(--faint);font-size:12.5px;font-family:ui-monospace,Menlo,monospace">Source &middot; github.com/zionboggan/security-research-notebook &middot; writeups/aiven/dragonfly-stream-restore-oom.md</p>
319
</div></div></section>
320
<footer><div class="wrap row">
321
  <div class="links"><a href="/">Portfolio</a><a href="https://www.linkedin.com/in/zion-boggan">LinkedIn</a><a href="/security-research-notebook/">Notebook</a><a href="mailto:zionboggan0@gmail.com">Email</a></div>
322
  <div class="note">Coordinated-disclosure research. Findings appear here only after the program's disclosure window closed, the patch shipped, or a CVE was published. No customer data was accessed.</div>
323
</div></footer>
324
</body></html>