GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bootstrap: check more metadata when loading the snapshot · nodejs/node@e638ea4

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • doc/api
    • cli.md
  • src
    • env.cc
    • env.h
    • node.cc
    • node_internals.h
    • node_snapshotable.cc
  • test/parallel
    • test-snapshot-incompatible.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+9Lines changed: 9 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,15 @@ in the current working directory.
11851185
When used without `--build-snapshot`, `--snapshot-blob` specifies the
11861186
path to the blob that will be used to restore the application state.
11871187

1188+
When loading a snapshot, Node.js checks that:
1189+
1190+
1. The version, architecture and platform of the running Node.js binary
1191+
are exactly the same as that of the binary that generates the snapshot.
1192+
2. The V8 flags and CPU features are compatible with that of the binary
1193+
that generates the snapshot.
1194+
1195+
If they don't match, Node.js would refuse to load the snapshot and exit with 1.
1196+
11881197
### `--test`
11891198

11901199
<!-- YAML
Collapse file

‎src/env.cc‎

Copy file name to clipboardExpand all lines: src/env.cc
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,21 @@ std::ostream& operator<<(std::ostream& output,
265265
return output;
266266
}
267267

268+
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) {
269+
output << "{\n"
270+
<< " "
271+
<< (i.type == SnapshotMetadata::Type::kDefault
272+
? "SnapshotMetadata::Type::kDefault"
273+
: "SnapshotMetadata::Type::kFullyCustomized")
274+
<< ", // type\n"
275+
<< " \"" << i.node_version << "\", // node_version\n"
276+
<< " \"" << i.node_arch << "\", // node_arch\n"
277+
<< " \"" << i.node_platform << "\", // node_platform\n"
278+
<< " " << i.v8_cache_version_tag << ", // v8_cache_version_tag\n"
279+
<< "}";
280+
return output;
281+
}
282+
268283
IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) {
269284
Isolate* isolate = creator->GetIsolate();
270285
IsolateDataSerializeInfo info;
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,19 @@ struct EnvSerializeInfo {
954954
friend std::ostream& operator<<(std::ostream& o, const EnvSerializeInfo& i);
955955
};
956956

957+
struct SnapshotMetadata {
958+
// For now kFullyCustomized is only built with the --build-snapshot CLI flag.
959+
// We might want to add more types of snapshots in the future.
960+
enum class Type : uint8_t { kDefault, kFullyCustomized };
961+
962+
Type type;
963+
std::string node_version;
964+
std::string node_arch;
965+
std::string node_platform;
966+
// Result of v8::ScriptCompiler::CachedDataVersionTag().
967+
uint32_t v8_cache_version_tag;
968+
};
969+
957970
struct SnapshotData {
958971
enum class DataOwnership { kOwned, kNotOwned };
959972

@@ -964,6 +977,8 @@ struct SnapshotData {
964977

965978
DataOwnership data_ownership = DataOwnership::kOwned;
966979

980+
SnapshotMetadata metadata;
981+
967982
// The result of v8::SnapshotCreator::CreateBlob() during the snapshot
968983
// building process.
969984
v8::StartupData v8_snapshot_blob_data{nullptr, 0};
@@ -980,7 +995,10 @@ struct SnapshotData {
980995
std::vector<builtins::CodeCacheInfo> code_cache;
981996

982997
void ToBlob(FILE* out) const;
983-
static void FromBlob(SnapshotData* out, FILE* in);
998+
// If returns false, the metadata doesn't match the current Node.js binary,
999+
// and the caller should not consume the snapshot data.
1000+
bool Check() const;
1001+
static bool FromBlob(SnapshotData* out, FILE* in);
9841002

9851003
~SnapshotData();
9861004

Collapse file

‎src/node.cc‎

Copy file name to clipboardExpand all lines: src/node.cc
+12-2Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,13 +1279,23 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
12791279
return exit_code;
12801280
}
12811281
std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>();
1282-
SnapshotData::FromBlob(read_data.get(), fp);
1282+
if (!SnapshotData::FromBlob(read_data.get(), fp)) {
1283+
// If we fail to read the customized snapshot, simply exit with 1.
1284+
exit_code = 1;
1285+
return exit_code;
1286+
}
12831287
*snapshot_data_ptr = read_data.release();
12841288
fclose(fp);
12851289
} else if (per_process::cli_options->node_snapshot) {
12861290
// If --snapshot-blob is not specified, we are reading the embedded
12871291
// snapshot, but we will skip it if --no-node-snapshot is specified.
1288-
*snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData();
1292+
const node::SnapshotData* read_data =
1293+
SnapshotBuilder::GetEmbeddedSnapshotData();
1294+
if (read_data != nullptr && read_data->Check()) {
1295+
// If we fail to read the embedded snapshot, treat it as if Node.js
1296+
// was built without one.
1297+
*snapshot_data_ptr = read_data;
1298+
}
12891299
}
12901300

12911301
if ((*snapshot_data_ptr) != nullptr) {
Collapse file

‎src/node_internals.h‎

Copy file name to clipboardExpand all lines: src/node_internals.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ std::ostream& operator<<(std::ostream& output,
416416
const TickInfo::SerializeInfo& d);
417417
std::ostream& operator<<(std::ostream& output,
418418
const AsyncHooks::SerializeInfo& d);
419+
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& d);
419420

420421
namespace performance {
421422
std::ostream& operator<<(std::ostream& output,
Collapse file

‎src/node_snapshotable.cc‎

Copy file name to clipboardExpand all lines: src/node_snapshotable.cc
+128-14Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,57 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) {
681681
return written_total;
682682
}
683683

684+
// Layout of SnapshotMetadata
685+
// [ 1 byte ] type of the snapshot
686+
// [ 4/8 bytes ] length of the node version string
687+
// [ ... ] |length| bytes of node version
688+
// [ 4/8 bytes ] length of the node arch string
689+
// [ ... ] |length| bytes of node arch
690+
// [ 4/8 bytes ] length of the node platform string
691+
// [ ... ] |length| bytes of node platform
692+
// [ 4 bytes ] v8 cache version tag
693+
template <>
694+
SnapshotMetadata FileReader::Read() {
695+
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<SnapshotMetadata>()\n");
696+
697+
SnapshotMetadata result;
698+
result.type = static_cast<SnapshotMetadata::Type>(Read<uint8_t>());
699+
result.node_version = ReadString();
700+
result.node_arch = ReadString();
701+
result.node_platform = ReadString();
702+
result.v8_cache_version_tag = Read<uint32_t>();
703+
704+
if (is_debug) {
705+
std::string str = ToStr(result);
706+
Debug("Read<SnapshotMetadata>() %s\n", str.c_str());
707+
}
708+
return result;
709+
}
710+
711+
template <>
712+
size_t FileWriter::Write(const SnapshotMetadata& data) {
713+
if (is_debug) {
714+
std::string str = ToStr(data);
715+
Debug("\nWrite<SnapshotMetadata>() %s\n", str.c_str());
716+
}
717+
size_t written_total = 0;
718+
// We need the Node.js version, platform and arch to match because
719+
// Node.js may perform synchronizations that are platform-specific and they
720+
// can be changed in semver-patches.
721+
Debug("Write snapshot type %" PRIu8 "\n", static_cast<uint8_t>(data.type));
722+
written_total += Write<uint8_t>(static_cast<uint8_t>(data.type));
723+
Debug("Write Node.js version %s\n", data.node_version.c_str());
724+
written_total += WriteString(data.node_version);
725+
Debug("Write Node.js arch %s\n", data.node_arch);
726+
written_total += WriteString(data.node_arch);
727+
Debug("Write Node.js platform %s\n", data.node_platform);
728+
written_total += WriteString(data.node_platform);
729+
Debug("Write V8 cached data version tag %" PRIx32 "\n",
730+
data.v8_cache_version_tag);
731+
written_total += Write<uint32_t>(data.v8_cache_version_tag);
732+
return written_total;
733+
}
734+
684735
// Layout of the snapshot blob
685736
// [ 4 bytes ] kMagic
686737
// [ 4/8 bytes ] length of Node.js version string
@@ -697,13 +748,12 @@ void SnapshotData::ToBlob(FILE* out) const {
697748
w.Debug("SnapshotData::ToBlob()\n");
698749

699750
size_t written_total = 0;
751+
700752
// Metadata
701753
w.Debug("Write magic %" PRIx32 "\n", kMagic);
702754
written_total += w.Write<uint32_t>(kMagic);
703-
w.Debug("Write version %s\n", NODE_VERSION);
704-
written_total += w.WriteString(NODE_VERSION);
705-
w.Debug("Write arch %s\n", NODE_ARCH);
706-
written_total += w.WriteString(NODE_ARCH);
755+
w.Debug("Write metadata\n");
756+
written_total += w.Write<SnapshotMetadata>(metadata);
707757

708758
written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data);
709759
w.Debug("Write isolate_data_indices\n");
@@ -714,22 +764,22 @@ void SnapshotData::ToBlob(FILE* out) const {
714764
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
715765
}
716766

717-
void SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
767+
bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
718768
FileReader r(in);
719769
r.Debug("SnapshotData::FromBlob()\n");
720770

771+
DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
772+
721773
// Metadata
722774
uint32_t magic = r.Read<uint32_t>();
723-
r.Debug("Read magic %" PRIx64 "\n", magic);
775+
r.Debug("Read magic %" PRIx32 "\n", magic);
724776
CHECK_EQ(magic, kMagic);
725-
std::string version = r.ReadString();
726-
r.Debug("Read version %s\n", version.c_str());
727-
CHECK_EQ(version, NODE_VERSION);
728-
std::string arch = r.ReadString();
729-
r.Debug("Read arch %s\n", arch.c_str());
730-
CHECK_EQ(arch, NODE_ARCH);
777+
out->metadata = r.Read<SnapshotMetadata>();
778+
r.Debug("Read metadata\n");
779+
if (!out->Check()) {
780+
return false;
781+
}
731782

732-
DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
733783
out->v8_snapshot_blob_data = r.Read<v8::StartupData>();
734784
r.Debug("Read isolate_data_info\n");
735785
out->isolate_data_info = r.Read<IsolateDataSerializeInfo>();
@@ -738,6 +788,54 @@ void SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
738788
out->code_cache = r.ReadVector<builtins::CodeCacheInfo>();
739789

740790
r.Debug("SnapshotData::FromBlob() read %d bytes\n", r.read_total);
791+
return true;
792+
}
793+
794+
bool SnapshotData::Check() const {
795+
if (metadata.node_version != per_process::metadata.versions.node) {
796+
fprintf(stderr,
797+
"Failed to load the startup snapshot because it was built with"
798+
"Node.js version %s and the current Node.js version is %s.\n",
799+
metadata.node_version.c_str(),
800+
NODE_VERSION);
801+
return false;
802+
}
803+
804+
if (metadata.node_arch != per_process::metadata.arch) {
805+
fprintf(stderr,
806+
"Failed to load the startup snapshot because it was built with"
807+
"architecture %s and the architecture is %s.\n",
808+
metadata.node_arch.c_str(),
809+
NODE_ARCH);
810+
return false;
811+
}
812+
813+
if (metadata.node_platform != per_process::metadata.platform) {
814+
fprintf(stderr,
815+
"Failed to load the startup snapshot because it was built with"
816+
"platform %s and the current platform is %s.\n",
817+
metadata.node_platform.c_str(),
818+
NODE_PLATFORM);
819+
return false;
820+
}
821+
822+
uint32_t current_cache_version = v8::ScriptCompiler::CachedDataVersionTag();
823+
if (metadata.v8_cache_version_tag != current_cache_version &&
824+
metadata.type == SnapshotMetadata::Type::kFullyCustomized) {
825+
// For now we only do this check for the customized snapshots - we know
826+
// that the flags we use in the default snapshot are limited and safe
827+
// enough so we can relax the constraints for it.
828+
fprintf(stderr,
829+
"Failed to load the startup snapshot because it was built with "
830+
"a different version of V8 or with different V8 configurations.\n"
831+
"Expected tag %" PRIx32 ", read %" PRIx32 "\n",
832+
current_cache_version,
833+
metadata.v8_cache_version_tag);
834+
return false;
835+
}
836+
837+
// TODO(joyeecheung): check incompatible Node.js flags.
838+
return true;
741839
}
742840

743841
SnapshotData::~SnapshotData() {
@@ -824,6 +922,10 @@ static const int v8_snapshot_blob_size = )"
824922
// -- data_ownership begins --
825923
SnapshotData::DataOwnership::kNotOwned,
826924
// -- data_ownership ends --
925+
// -- metadata begins --
926+
)" << data->metadata
927+
<< R"(,
928+
// -- metadata ends --
827929
// -- v8_snapshot_blob_data begins --
828930
{ v8_snapshot_blob_data, v8_snapshot_blob_size },
829931
// -- v8_snapshot_blob_data ends --
@@ -920,6 +1022,12 @@ int SnapshotBuilder::Generate(SnapshotData* out,
9201022
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
9211023
});
9221024

1025+
// It's only possible to be kDefault in node_mksnapshot.
1026+
SnapshotMetadata::Type snapshot_type =
1027+
per_process::cli_options->build_snapshot
1028+
? SnapshotMetadata::Type::kFullyCustomized
1029+
: SnapshotMetadata::Type::kDefault;
1030+
9231031
{
9241032
HandleScope scope(isolate);
9251033
TryCatch bootstrapCatch(isolate);
@@ -982,7 +1090,7 @@ int SnapshotBuilder::Generate(SnapshotData* out,
9821090
// point (we currently only support this kind of entry point, but we
9831091
// could also explore snapshotting other kinds of execution modes
9841092
// in the future).
985-
if (per_process::cli_options->build_snapshot) {
1093+
if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) {
9861094
#if HAVE_INSPECTOR
9871095
// TODO(joyeecheung): move this before RunBootstrapping().
9881096
env->InitializeInspector({});
@@ -1050,6 +1158,12 @@ int SnapshotBuilder::Generate(SnapshotData* out,
10501158
return SNAPSHOT_ERROR;
10511159
}
10521160

1161+
out->metadata = SnapshotMetadata{snapshot_type,
1162+
per_process::metadata.versions.node,
1163+
per_process::metadata.arch,
1164+
per_process::metadata.platform,
1165+
v8::ScriptCompiler::CachedDataVersionTag()};
1166+
10531167
// We cannot resurrect the handles from the snapshot, so make sure that
10541168
// no handles are left open in the environment after the blob is created
10551169
// (which should trigger a GC and close all handles that can be closed).
Collapse file

‎test/parallel/test-snapshot-incompatible.js‎

Copy file name to clipboard
+76Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
3+
// This tests that Node.js refuses to load snapshots built with incompatible
4+
// V8 configurations.
5+
6+
require('../common');
7+
const assert = require('assert');
8+
const { spawnSync } = require('child_process');
9+
const tmpdir = require('../common/tmpdir');
10+
const fixtures = require('../common/fixtures');
11+
const path = require('path');
12+
const fs = require('fs');
13+
14+
tmpdir.refresh();
15+
const blobPath = path.join(tmpdir.path, 'snapshot.blob');
16+
const entry = fixtures.path('empty.js');
17+
18+
// The flag used can be any flag that makes a difference in
19+
// v8::ScriptCompiler::CachedDataVersionTag(). --harmony
20+
// is chosen here because it's stable enough and makes a difference.
21+
{
22+
// Build a snapshot with --harmony.
23+
const child = spawnSync(process.execPath, [
24+
'--harmony',
25+
'--snapshot-blob',
26+
blobPath,
27+
'--build-snapshot',
28+
entry,
29+
], {
30+
cwd: tmpdir.path
31+
});
32+
if (child.status !== 0) {
33+
console.log(child.stderr.toString());
34+
console.log(child.stdout.toString());
35+
assert.strictEqual(child.status, 0);
36+
}
37+
const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob'));
38+
assert(stats.isFile());
39+
}
40+
41+
{
42+
// Now load the snapshot without --harmony, which should fail.
43+
const child = spawnSync(process.execPath, [
44+
'--snapshot-blob',
45+
blobPath,
46+
], {
47+
cwd: tmpdir.path,
48+
env: {
49+
...process.env,
50+
}
51+
});
52+
53+
const stderr = child.stderr.toString().trim();
54+
assert.match(stderr, /Failed to load the startup snapshot/);
55+
assert.strictEqual(child.status, 1);
56+
}
57+
58+
{
59+
// Load it again with --harmony and it should work.
60+
const child = spawnSync(process.execPath, [
61+
'--harmony',
62+
'--snapshot-blob',
63+
blobPath,
64+
], {
65+
cwd: tmpdir.path,
66+
env: {
67+
...process.env,
68+
}
69+
});
70+
71+
if (child.status !== 0) {
72+
console.log(child.stderr.toString());
73+
console.log(child.stdout.toString());
74+
assert.strictEqual(child.status, 0);
75+
}
76+
}

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.