Skip to content

Commit 5334e50

Browse files
sea: error instead of SIGSEGV when fuse set without blob
When the postject fuse byte is set but NODE_SEA_BLOB cannot be found, FindSingleExecutableBlob previously constructed a null string_view and BlobDeserializer NULL-dereferenced. Exit with a clear error instead. Fixes: #63466 Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
1 parent 6a6dee3 commit 5334e50

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/node_sea_bin.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "util-inl.h"
2121

2222
#include <algorithm>
23+
#include <cstdlib>
2324
#include <memory>
2425
#include <string>
2526
#include <string_view>
@@ -40,6 +41,7 @@
4041

4142
namespace node {
4243
namespace sea {
44+
using node::ExitCode;
4345

4446
// TODO(joyeecheung): use LIEF to locate it directly.
4547
std::string_view FindSingleExecutableBlob() {
@@ -57,6 +59,23 @@ std::string_view FindSingleExecutableBlob() {
5759
const char* blob = static_cast<const char*>(
5860
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
5961
#endif
62+
// Fuse set with no (or empty) blob used to NULL-deref in BlobDeserializer.
63+
// See https://github.com/nodejs/node/issues/63466.
64+
if (blob == nullptr || size == 0) {
65+
char exec_path_buf[2 * PATH_MAX];
66+
size_t exec_path_len = sizeof(exec_path_buf);
67+
const char* path = "this binary";
68+
if (uv_exepath(exec_path_buf, &exec_path_len) == 0) {
69+
path = exec_path_buf;
70+
}
71+
FPrintF(stderr,
72+
"node: SEA fuse is set but no valid NODE_SEA_BLOB resource "
73+
"was found in %s.\n"
74+
"The host binary may be missing a PT_NOTE program header "
75+
"(run `readelf -lW <binary> | grep NOTE` to check).\n",
76+
path);
77+
exit(static_cast<int>(ExitCode::kGenericUserError));
78+
}
6079
return {blob, size};
6180
}();
6281
per_process::Debug(DebugCategory::SEA,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
// Verifies that a host binary with the SEA fuse set but without a
4+
// NODE_SEA_BLOB resource exits with a clear error instead of SIGSEGV.
5+
// Regression test for https://github.com/nodejs/node/issues/63466.
6+
7+
require('../common');
8+
9+
const {
10+
skipIfSingleExecutableIsNotSupported,
11+
signSEA,
12+
} = require('../common/sea');
13+
14+
skipIfSingleExecutableIsNotSupported();
15+
16+
const tmpdir = require('../common/tmpdir');
17+
const { copyFileSync, readFileSync, writeFileSync, chmodSync } = require('fs');
18+
const { join } = require('path');
19+
const { spawnSyncAndAssert } = require('../common/child_process');
20+
21+
tmpdir.refresh();
22+
23+
const fusedBinary = join(tmpdir.path, process.platform === 'win32' ? 'fused.exe' : 'fused');
24+
copyFileSync(process.execPath, fusedBinary);
25+
26+
const fuse = Buffer.from('NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2');
27+
const buf = readFileSync(fusedBinary);
28+
const fuseAt = buf.indexOf(fuse);
29+
if (fuseAt === -1) {
30+
require('../common').skip('SEA fuse sentinel not found in process.execPath');
31+
}
32+
33+
const fuseValueOffset = fuseAt + fuse.length + 1; // skip ':'
34+
if (buf[fuseValueOffset] !== 0x30 /* '0' */) {
35+
require('../common').skip(`Unexpected SEA fuse value: ${buf[fuseValueOffset]}`);
36+
}
37+
38+
buf[fuseValueOffset] = 0x31; // '1'
39+
writeFileSync(fusedBinary, buf);
40+
chmodSync(fusedBinary, 0o755);
41+
signSEA(fusedBinary);
42+
43+
spawnSyncAndAssert(
44+
fusedBinary,
45+
['--version'],
46+
{},
47+
{
48+
status: 1,
49+
signal: null,
50+
stderr: /SEA fuse is set but no valid NODE_SEA_BLOB resource was found/,
51+
},
52+
);

0 commit comments

Comments
 (0)