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@98fa488

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
@@ -1171,6 +1171,15 @@ in the current working directory.
11711171
When used without `--build-snapshot`, `--snapshot-blob` specifies the
11721172
path to the blob that will be used to restore the application state.
11731173

1174+
When loading a snapshot, Node.js checks that:
1175+
1176+
1. The version, architecture and platform of the running Node.js binary
1177+
are exactly the same as that of the binary that generates the snapshot.
1178+
2. The V8 flags and CPU features are compatible with that of the binary
1179+
that generates the snapshot.
1180+
1181+
If they don't match, Node.js would refuse to load the snapshot and exit with 1.
1182+
11741183
### `--test`
11751184

11761185
<!-- 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
@@ -261,6 +261,21 @@ std::ostream& operator<<(std::ostream& output,
261261
return output;
262262
}
263263

264+
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) {
265+
output << "{\n"
266+
<< " "
267+
<< (i.type == SnapshotMetadata::Type::kDefault
268+
? "SnapshotMetadata::Type::kDefault"
269+
: "SnapshotMetadata::Type::kFullyCustomized")
270+
<< ", // type\n"
271+
<< " \"" << i.node_version << "\", // node_version\n"
272+
<< " \"" << i.node_arch << "\", // node_arch\n"
273+
<< " \"" << i.node_platform << "\", // node_platform\n"
274+
<< " " << i.v8_cache_version_tag << ", // v8_cache_version_tag\n"
275+
<< "}";
276+
return output;
277+
}
278+
264279
IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) {
265280
Isolate* isolate = creator->GetIsolate();
266281
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
@@ -984,6 +984,19 @@ struct EnvSerializeInfo {
984984
friend std::ostream& operator<<(std::ostream& o, const EnvSerializeInfo& i);
985985
};
986986

987+
struct SnapshotMetadata {
988+
// For now kFullyCustomized is only built with the --build-snapshot CLI flag.
989+
// We might want to add more types of snapshots in the future.
990+
enum class Type : uint8_t { kDefault, kFullyCustomized };
991+
992+
Type type;
993+
std::string node_version;
994+
std::string node_arch;
995+
std::string node_platform;
996+
// Result of v8::ScriptCompiler::CachedDataVersionTag().
997+
uint32_t v8_cache_version_tag;
998+
};
999+
9871000
struct SnapshotData {
9881001
enum class DataOwnership { kOwned, kNotOwned };
9891002

@@ -993,6 +1006,8 @@ struct SnapshotData {
9931006

9941007
DataOwnership data_ownership = DataOwnership::kOwned;
9951008

1009+
SnapshotMetadata metadata;
1010+
9961011
// The result of v8::SnapshotCreator::CreateBlob() during the snapshot
9971012
// building process.
9981013
v8::StartupData v8_snapshot_blob_data{nullptr, 0};
@@ -1009,7 +1024,10 @@ struct SnapshotData {
10091024
std::vector<builtins::CodeCacheInfo> code_cache;
10101025

10111026
void ToBlob(FILE* out) const;
1012-
static void FromBlob(SnapshotData* out, FILE* in);
1027+
// If returns false, the metadata doesn't match the current Node.js binary,
1028+
// and the caller should not consume the snapshot data.
1029+
bool Check() const;
1030+
static bool FromBlob(SnapshotData* out, FILE* in);
10131031

10141032
~SnapshotData();
10151033

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
@@ -1269,13 +1269,23 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
12691269
return exit_code;
12701270
}
12711271
std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>();
1272-
SnapshotData::FromBlob(read_data.get(), fp);
1272+
if (!SnapshotData::FromBlob(read_data.get(), fp)) {
1273+
// If we fail to read the customized snapshot, simply exit with 1.
1274+
exit_code = 1;
1275+
return exit_code;
1276+
}
12731277
*snapshot_data_ptr = read_data.release();
12741278
fclose(fp);
12751279
} else if (per_process::cli_options->node_snapshot) {
12761280
// If --snapshot-blob is not specified, we are reading the embedded
12771281
// snapshot, but we will skip it if --no-node-snapshot is specified.
1278-
*snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData();
1282+
const node::SnapshotData* read_data =
1283+
SnapshotBuilder::GetEmbeddedSnapshotData();
1284+
if (read_data != nullptr && read_data->Check()) {
1285+
// If we fail to read the embedded snapshot, treat it as if Node.js
1286+
// was built without one.
1287+
*snapshot_data_ptr = read_data;
1288+
}
12791289
}
12801290

12811291
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
@@ -414,6 +414,7 @@ std::ostream& operator<<(std::ostream& output,
414414
const TickInfo::SerializeInfo& d);
415415
std::ostream& operator<<(std::ostream& output,
416416
const AsyncHooks::SerializeInfo& d);
417+
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& d);
417418

418419
namespace performance {
419420
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
@@ -679,6 +679,57 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) {
679679
return written_total;
680680
}
681681

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

697748
size_t written_total = 0;
749+
698750
// Metadata
699751
w.Debug("Write magic %" PRIx32 "\n", kMagic);
700752
written_total += w.Write<uint32_t>(kMagic);
701-
w.Debug("Write version %s\n", NODE_VERSION);
702-
written_total += w.WriteString(NODE_VERSION);
703-
w.Debug("Write arch %s\n", NODE_ARCH);
704-
written_total += w.WriteString(NODE_ARCH);
753+
w.Debug("Write metadata\n");
754+
written_total += w.Write<SnapshotMetadata>(metadata);
705755

706756
written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data);
707757
w.Debug("Write isolate_data_indices\n");
@@ -712,22 +762,22 @@ void SnapshotData::ToBlob(FILE* out) const {
712762
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
713763
}
714764

715-
void SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
765+
bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
716766
FileReader r(in);
717767
r.Debug("SnapshotData::FromBlob()\n");
718768

769+
DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
770+
719771
// Metadata
720772
uint32_t magic = r.Read<uint32_t>();
721-
r.Debug("Read magic %" PRIx64 "\n", magic);
773+
r.Debug("Read magic %" PRIx32 "\n", magic);
722774
CHECK_EQ(magic, kMagic);
723-
std::string version = r.ReadString();
724-
r.Debug("Read version %s\n", version.c_str());
725-
CHECK_EQ(version, NODE_VERSION);
726-
std::string arch = r.ReadString();
727-
r.Debug("Read arch %s\n", arch.c_str());
728-
CHECK_EQ(arch, NODE_ARCH);
775+
out->metadata = r.Read<SnapshotMetadata>();
776+
r.Debug("Read metadata\n");
777+
if (!out->Check()) {
778+
return false;
779+
}
729780

730-
DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
731781
out->v8_snapshot_blob_data = r.Read<v8::StartupData>();
732782
r.Debug("Read isolate_data_info\n");
733783
out->isolate_data_info = r.Read<IsolateDataSerializeInfo>();
@@ -736,6 +786,54 @@ void SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
736786
out->code_cache = r.ReadVector<builtins::CodeCacheInfo>();
737787

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

741839
SnapshotData::~SnapshotData() {
@@ -822,6 +920,10 @@ static const int v8_snapshot_blob_size = )"
822920
// -- data_ownership begins --
823921
SnapshotData::DataOwnership::kNotOwned,
824922
// -- data_ownership ends --
923+
// -- metadata begins --
924+
)" << data->metadata
925+
<< R"(,
926+
// -- metadata ends --
825927
// -- v8_snapshot_blob_data begins --
826928
{ v8_snapshot_blob_data, v8_snapshot_blob_size },
827929
// -- v8_snapshot_blob_data ends --
@@ -908,6 +1010,12 @@ int SnapshotBuilder::Generate(SnapshotData* out,
9081010
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
9091011
});
9101012

1013+
// It's only possible to be kDefault in node_mksnapshot.
1014+
SnapshotMetadata::Type snapshot_type =
1015+
per_process::cli_options->build_snapshot
1016+
? SnapshotMetadata::Type::kFullyCustomized
1017+
: SnapshotMetadata::Type::kDefault;
1018+
9111019
{
9121020
HandleScope scope(isolate);
9131021
TryCatch bootstrapCatch(isolate);
@@ -956,7 +1064,7 @@ int SnapshotBuilder::Generate(SnapshotData* out,
9561064
// point (we currently only support this kind of entry point, but we
9571065
// could also explore snapshotting other kinds of execution modes
9581066
// in the future).
959-
if (per_process::cli_options->build_snapshot) {
1067+
if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) {
9601068
#if HAVE_INSPECTOR
9611069
// TODO(joyeecheung): move this before RunBootstrapping().
9621070
env->InitializeInspector({});
@@ -1020,6 +1128,12 @@ int SnapshotBuilder::Generate(SnapshotData* out,
10201128
return SNAPSHOT_ERROR;
10211129
}
10221130

1131+
out->metadata = SnapshotMetadata{snapshot_type,
1132+
per_process::metadata.versions.node,
1133+
per_process::metadata.arch,
1134+
per_process::metadata.platform,
1135+
v8::ScriptCompiler::CachedDataVersionTag()};
1136+
10231137
// We cannot resurrect the handles from the snapshot, so make sure that
10241138
// no handles are left open in the environment after the blob is created
10251139
// (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.