Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/node_sea_bin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "util-inl.h"

#include <algorithm>
#include <cstdlib>
#include <memory>
#include <string>
#include <string_view>
Expand All @@ -40,6 +41,7 @@

namespace node {
namespace sea {
using node::ExitCode;

// TODO(joyeecheung): use LIEF to locate it directly.
std::string_view FindSingleExecutableBlob() {
Expand All @@ -57,6 +59,23 @@ std::string_view FindSingleExecutableBlob() {
const char* blob = static_cast<const char*>(
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
#endif
// Fuse set with no (or empty) blob used to NULL-deref in BlobDeserializer.
// See https://github.com/nodejs/node/issues/63466.
if (blob == nullptr || size == 0) {
char exec_path_buf[2 * PATH_MAX];
size_t exec_path_len = sizeof(exec_path_buf);
const char* path = "this binary";
if (uv_exepath(exec_path_buf, &exec_path_len) == 0) {
path = exec_path_buf;
}
FPrintF(stderr,
"node: SEA fuse is set but no valid NODE_SEA_BLOB resource "
"was found in %s.\n"
"The host binary may be missing a PT_NOTE program header "
"(run `readelf -lW <binary> | grep NOTE` to check).\n",
path);
exit(static_cast<int>(ExitCode::kGenericUserError));
}
return {blob, size};
}();
per_process::Debug(DebugCategory::SEA,
Expand Down
52 changes: 52 additions & 0 deletions test/sea/test-single-executable-application-missing-blob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

// Verifies that a host binary with the SEA fuse set but without a
// NODE_SEA_BLOB resource exits with a clear error instead of SIGSEGV.
// Regression test for https://github.com/nodejs/node/issues/63466.

require('../common');

const {
skipIfSingleExecutableIsNotSupported,
signSEA,
} = require('../common/sea');

skipIfSingleExecutableIsNotSupported();

const tmpdir = require('../common/tmpdir');
const { copyFileSync, readFileSync, writeFileSync, chmodSync } = require('fs');
const { join } = require('path');
const { spawnSyncAndAssert } = require('../common/child_process');

tmpdir.refresh();

const fusedBinary = join(tmpdir.path, process.platform === 'win32' ? 'fused.exe' : 'fused');
copyFileSync(process.execPath, fusedBinary);

const fuse = Buffer.from('NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2');
const buf = readFileSync(fusedBinary);
const fuseAt = buf.indexOf(fuse);
if (fuseAt === -1) {
require('../common').skip('SEA fuse sentinel not found in process.execPath');
}

const fuseValueOffset = fuseAt + fuse.length + 1; // skip ':'
if (buf[fuseValueOffset] !== 0x30 /* '0' */) {
require('../common').skip(`Unexpected SEA fuse value: ${buf[fuseValueOffset]}`);
}

buf[fuseValueOffset] = 0x31; // '1'
writeFileSync(fusedBinary, buf);
chmodSync(fusedBinary, 0o755);
signSEA(fusedBinary);

spawnSyncAndAssert(
fusedBinary,
['--version'],
{},
{
status: 1,
signal: null,
stderr: /SEA fuse is set but no valid NODE_SEA_BLOB resource was found/,
},
);