From ef17d6edacf9cd21bd089de99b59bd491149e9a8 Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Thu, 30 Jul 2026 06:59:35 -0500 Subject: [PATCH] accept cargo-owned windows hardlinks --- .github/scripts/cargo-build-artifacts.mjs | 292 ++++++++++++++++-- .../scripts/cargo-build-artifacts.test.mjs | 242 ++++++++++++++- 2 files changed, 483 insertions(+), 51 deletions(-) diff --git a/.github/scripts/cargo-build-artifacts.mjs b/.github/scripts/cargo-build-artifacts.mjs index 591d0a0ac..633e844f7 100644 --- a/.github/scripts/cargo-build-artifacts.mjs +++ b/.github/scripts/cargo-build-artifacts.mjs @@ -6,9 +6,10 @@ import path from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; -const SCHEMA = "codestory.cargo-build-artifacts/v1"; +const SCHEMA = "codestory.cargo-build-artifacts/v2"; const SHA40 = /^[0-9a-f]{40}$/u; const SHA256 = /^[0-9a-f]{64}$/u; +const DECIMAL_BIGINT = /^(?:0|[1-9][0-9]*)$/u; const ALIAS = /^[a-z][a-z0-9_]*$/u; const WINDOWS_TARGET = "x86_64-pc-windows-msvc"; const RELEASE_OPT_LEVELS = new Set(["1", "2", "3", "s", "z"]); @@ -226,11 +227,150 @@ function hashFile(file) { return hash.digest("hex"); } +function nativeFileMetadata(file, label) { + const metadata = fs.lstatSync(file, { bigint: true }); + if (!metadata.isFile() || metadata.isSymbolicLink()) { + fail(`${label} must be a regular, non-symlink file`); + } + if ( + metadata.dev < 0n + || metadata.ino <= 0n + || metadata.nlink <= 0n + || metadata.size < 0n + || metadata.size > BigInt(Number.MAX_SAFE_INTEGER) + ) { + fail(`${label} has unusable native filesystem metadata`); + } + return metadata; +} + +function nativeIdentity(metadata) { + return { + device: metadata.dev.toString(), + inode: metadata.ino.toString(), + }; +} + +function sameNativeIdentity(left, right) { + return left.dev === right.dev && left.ino === right.ino; +} + +function sameNativeSnapshot(left, right) { + return ( + sameNativeIdentity(left, right) + && left.nlink === right.nlink + && left.size === right.size + ); +} + +function profileRelativePath(realProfileRoot, candidate, label) { + const realCandidate = fs.realpathSync(candidate); + if (!isWithin(realProfileRoot, realCandidate)) { + fail(`${label} escaped the exact target release directory`); + } + return path.relative(realProfileRoot, realCandidate) + .split(path.sep) + .join("/"); +} + +function inspectNativeLinks({ + executable, + kind, + realProfileRoot, + targetName, +}) { + const label = `Cargo artifact ${targetName}`; + const before = nativeFileMetadata(executable, label); + if (before.nlink > BigInt(Number.MAX_SAFE_INTEGER)) { + fail(`${label} has an unusable native hardlink count`); + } + const linkCount = Number(before.nlink); + const selectedRelative = profileRelativePath( + realProfileRoot, + executable, + label, + ); + const paths = [selectedRelative]; + + if (kind === "test") { + if (linkCount !== 1) { + fail(`${label} test executable has native aliases outside its Cargo output path`); + } + } else if (linkCount === 2) { + const depsRoot = path.join(realProfileRoot, "deps"); + const depsMetadata = fs.lstatSync(depsRoot); + if (!depsMetadata.isDirectory() || depsMetadata.isSymbolicLink()) { + fail(`${label} hardlink peer directory is missing`); + } + const peer = path.join( + depsRoot, + `${targetName.replaceAll("-", "_")}.exe`, + ); + if (!fs.existsSync(peer)) { + fail( + `${label} hardlinks are not exactly the release-root executable and one release/deps peer`, + ); + } + const peerMetadata = nativeFileMetadata(peer, `${label} hardlink peer`); + if (!sameNativeIdentity(before, peerMetadata)) { + fail( + `${label} hardlinks are not exactly the release-root executable and one release/deps peer`, + ); + } + paths.push(profileRelativePath(realProfileRoot, peer, label)); + } else if (linkCount !== 1) { + fail(`${label} has an unsupported native hardlink count ${linkCount}`); + } + + paths.sort(); + if (paths.length !== linkCount || new Set(paths).size !== paths.length) { + fail(`${label} native hardlink accounting is incomplete`); + } + const after = nativeFileMetadata(executable, label); + if (!sameNativeSnapshot(before, after)) { + fail(`${label} native identity changed while its hardlinks were inspected`); + } + return { + metadata: after, + nativeLinks: { + ...nativeIdentity(after), + count: linkCount, + paths, + }, + }; +} + +function requireStableNativeInspection({ + executable, + expectedMetadata, + expectedNativeLinks, + kind, + label, + realProfileRoot, + targetName, +}) { + const current = inspectNativeLinks({ + executable, + kind, + realProfileRoot, + targetName, + }); + if ( + !sameNativeSnapshot(expectedMetadata, current.metadata) + || JSON.stringify(expectedNativeLinks) !== JSON.stringify(current.nativeLinks) + ) { + fail(`${label} native identity changed while its contents were authenticated`); + } +} + function validatedExecutable(message, { kind, profileRoot, targetName, }) { + if (message.fresh !== false) { + fail(`Cargo artifact ${targetName} was not produced by the exact build invocation`); + } if (typeof message.executable !== "string" || message.executable === "") { fail(`Cargo artifact ${targetName} did not emit an executable`); } @@ -243,38 +383,51 @@ function validatedExecutable(message, { fail(`Cargo artifact ${targetName} executable is not one of its emitted filenames`); } const executable = path.resolve(message.executable); - const metadata = fs.lstatSync(executable); - if ( - !metadata.isFile() - || metadata.isSymbolicLink() - || metadata.nlink !== 1 - ) { - fail( - `Cargo artifact ${targetName} must be a regular, non-symlink, singly linked file`, - ); - } if (path.extname(executable).toLowerCase() !== ".exe") { fail(`Cargo artifact ${targetName} is not a Windows executable`); } const realProfileRoot = fs.realpathSync(profileRoot); - const realExecutable = fs.realpathSync(executable); - if (!isWithin(realProfileRoot, realExecutable)) { - fail(`Cargo artifact ${targetName} escaped the exact target release directory`); - } - const relative = path.relative(realProfileRoot, realExecutable); - if (kind === "test" && path.dirname(relative) !== "deps") { + const relative = profileRelativePath( + realProfileRoot, + executable, + `Cargo artifact ${targetName}`, + ); + if (kind === "test" && path.posix.dirname(relative) !== "deps") { fail(`Cargo test artifact ${targetName} was not emitted under release/deps`); } - if (kind === "bin" && path.dirname(relative) !== ".") { + if ( + kind === "bin" + && ( + path.posix.dirname(relative) !== "." + || path.posix.basename(relative) !== `${targetName}.exe` + ) + ) { fail(`Cargo binary artifact ${targetName} was not emitted at the release root`); } + const { metadata, nativeLinks } = inspectNativeLinks({ + executable, + kind, + realProfileRoot, + targetName, + }); + const sha256 = hashFile(executable); + requireStableNativeInspection({ + executable, + expectedMetadata: metadata, + expectedNativeLinks: nativeLinks, + kind, + label: `Cargo artifact ${targetName}`, + realProfileRoot, + targetName, + }); return { path: executable, - relative_path: relative.split(path.sep).join("/"), - bytes: metadata.size, - sha256: hashFile(executable), + relative_path: relative, + bytes: Number(metadata.size), + sha256, + native_links: nativeLinks, }; } @@ -422,6 +575,58 @@ function validateManifestShape(manifest) { } } +function validateNativeLinksShape(nativeLinks, artifact, alias) { + exactKeys( + nativeLinks, + ["device", "inode", "count", "paths"], + `artifact manifest native links ${alias}`, + ); + if ( + typeof nativeLinks.device !== "string" + || !DECIMAL_BIGINT.test(nativeLinks.device) + || typeof nativeLinks.inode !== "string" + || !DECIMAL_BIGINT.test(nativeLinks.inode) + || BigInt(nativeLinks.inode) <= 0n + || !Number.isSafeInteger(nativeLinks.count) + || ![1, 2].includes(nativeLinks.count) + || !Array.isArray(nativeLinks.paths) + || nativeLinks.paths.length !== nativeLinks.count + || new Set(nativeLinks.paths).size !== nativeLinks.paths.length + || nativeLinks.paths.some( + (entry) => + typeof entry !== "string" + || entry === "" + || entry.includes("\\") + || path.posix.isAbsolute(entry) + || entry === ".." + || entry.startsWith("../"), + ) + || JSON.stringify(nativeLinks.paths) !== JSON.stringify([...nativeLinks.paths].sort()) + || !nativeLinks.paths.includes(artifact.relative_path) + ) { + fail(`artifact manifest native links ${alias} values changed`); + } + const peerPaths = nativeLinks.paths.filter( + (entry) => entry !== artifact.relative_path, + ); + if ( + (artifact.kind === "test" && nativeLinks.count !== 1) + || ( + artifact.kind === "bin" + && nativeLinks.count === 2 + && ( + peerPaths.length !== 1 + || path.posix.dirname(peerPaths[0]) !== "deps" + || path.posix.basename(peerPaths[0]) + !== `${artifact.target.replaceAll("-", "_")}.exe` + ) + ) + || (artifact.kind === "bin" && nativeLinks.count === 1 && peerPaths.length !== 0) + ) { + fail(`artifact manifest native link topology changed for ${alias}`); + } +} + export function verifyCargoArtifactManifest({ exactSha, exactTree, @@ -465,6 +670,7 @@ export function verifyCargoArtifactManifest({ "relative_path", "bytes", "sha256", + "native_links", ], `artifact manifest entry ${alias}`, ); @@ -492,17 +698,8 @@ export function verifyCargoArtifactManifest({ fail(`artifact manifest contract changed for ${alias}`); } releaseProfile({ profile: artifact.profile }, artifact.kind); + validateNativeLinksShape(artifact.native_links, artifact, alias); const executable = path.resolve(artifact.path); - const metadata = fs.lstatSync(executable); - if ( - !metadata.isFile() - || metadata.isSymbolicLink() - || metadata.nlink !== 1 - ) { - fail( - `artifact ${alias} is no longer a regular, non-symlink, singly linked file`, - ); - } const realExecutable = fs.realpathSync(executable); if (!isWithin(realProfileRoot, realExecutable)) { fail(`artifact ${alias} escaped the exact target release directory`); @@ -512,15 +709,42 @@ export function verifyCargoArtifactManifest({ .join("/"); if ( (artifact.kind === "test" && path.posix.dirname(relative) !== "deps") - || (artifact.kind === "bin" && path.posix.dirname(relative) !== ".") + || ( + artifact.kind === "bin" + && ( + path.posix.dirname(relative) !== "." + || path.posix.basename(relative) !== `${artifact.target}.exe` + ) + ) || path.extname(executable).toLowerCase() !== ".exe" ) { fail(`artifact ${alias} no longer has its expected release-graph path`); } + const { metadata, nativeLinks } = inspectNativeLinks({ + executable, + kind: artifact.kind, + realProfileRoot, + targetName: artifact.target, + }); + if ( + JSON.stringify(nativeLinks) !== JSON.stringify(artifact.native_links) + ) { + fail(`artifact ${alias} no longer matches its authenticated native links`); + } + const sha256 = hashFile(executable); + requireStableNativeInspection({ + executable, + expectedMetadata: metadata, + expectedNativeLinks: nativeLinks, + kind: artifact.kind, + label: `artifact ${alias}`, + realProfileRoot, + targetName: artifact.target, + }); if ( relative !== artifact.relative_path - || metadata.size !== artifact.bytes - || hashFile(executable) !== artifact.sha256 + || Number(metadata.size) !== artifact.bytes + || sha256 !== artifact.sha256 ) { fail(`artifact ${alias} no longer matches its authenticated build output`); } diff --git a/.github/scripts/cargo-build-artifacts.test.mjs b/.github/scripts/cargo-build-artifacts.test.mjs index 5824c1ef6..2b721de9e 100644 --- a/.github/scripts/cargo-build-artifacts.test.mjs +++ b/.github/scripts/cargo-build-artifacts.test.mjs @@ -164,10 +164,33 @@ function build(input = fixture()) { return { input, manifest }; } +function addCargoBinPeer(input, alias = "cli", peerName) { + const artifact = input.artifacts.find((entry) => entry.alias === alias); + assert.ok(artifact); + assert.equal(artifact.kind, "bin"); + const peer = path.join( + input.releaseDir, + "deps", + peerName ?? `${artifact.targetName.replaceAll("-", "_")}.exe`, + ); + fs.linkSync(artifact.executable, peer); + return peer; +} + +function verify(input, manifest, exactSha = SOURCE_SHA) { + return verifyCargoArtifactManifest({ + exactSha, + exactTree: SOURCE_TREE, + manifest, + rustTarget: RUST_TARGET, + workspaceRoot: input.root, + }); +} + test("binds each requested executable to the exact Windows release graph", () => { const { input, manifest } = build(); - assert.equal(manifest.schema, "codestory.cargo-build-artifacts/v1"); + assert.equal(manifest.schema, "codestory.cargo-build-artifacts/v2"); assert.deepEqual(manifest.source, { commit: SOURCE_SHA, tree: SOURCE_TREE, @@ -180,16 +203,14 @@ test("binds each requested executable to the exact Windows release graph", () => assert.equal(selected.bytes, Buffer.byteLength(artifact.contents)); assert.equal(selected.sha256, sha256(artifact.contents)); assert.equal(selected.profile.test, artifact.kind === "test"); + assert.equal(selected.native_links.count, 1); + assert.deepEqual(selected.native_links.paths, [selected.relative_path]); + assert.match(selected.native_links.device, /^(?:0|[1-9][0-9]*)$/u); + assert.match(selected.native_links.inode, /^[1-9][0-9]*$/u); } assert.deepEqual( - verifyCargoArtifactManifest({ - exactSha: SOURCE_SHA, - exactTree: SOURCE_TREE, - manifest, - rustTarget: RUST_TARGET, - workspaceRoot: input.root, - }), + verify(input, manifest), Object.fromEntries( input.artifacts.map((artifact) => [ artifact.alias, @@ -199,6 +220,38 @@ test("binds each requested executable to the exact Windows release graph", () => ); }); +test("accepts and records Cargo's release-root hardlink to release/deps", () => { + const input = fixture(); + const peer = addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + const selected = manifest.artifacts.cli; + + assert.equal(selected.native_links.count, 2); + assert.deepEqual(selected.native_links.paths, [ + "codestory-cli.exe", + "deps/codestory_cli.exe", + ]); + const rootIdentity = fs.lstatSync(input.artifacts[0].executable, { bigint: true }); + const peerIdentity = fs.lstatSync(peer, { bigint: true }); + assert.equal(rootIdentity.dev, peerIdentity.dev); + assert.equal(rootIdentity.ino, peerIdentity.ino); + assert.equal(rootIdentity.nlink, 2n); + assert.equal(selected.native_links.device, rootIdentity.dev.toString()); + assert.equal(selected.native_links.inode, rootIdentity.ino.toString()); + + assert.equal(verify(input, manifest).cli, path.resolve(input.artifacts[0].executable)); +}); + +test("accepts Cargo's copied release-root fallback when native hardlinking is unavailable", () => { + const { input, manifest } = build(); + + assert.equal(manifest.artifacts.cli.native_links.count, 1); + assert.deepEqual(manifest.artifacts.cli.native_links.paths, [ + "codestory-cli.exe", + ]); + assert.doesNotThrow(() => verify(input, manifest)); +}); + test("accepts the release graph without the optional qualification driver", () => { const input = fixture({ includeQualificationDriver: false }); const { manifest } = build(input); @@ -229,6 +282,17 @@ test("rejects duplicate compiler artifacts instead of choosing one by path order ); }); +test("rejects a fresh Cargo artifact from a prior build invocation", () => { + const input = fixture(); + input.messages[1].fresh = true; + input.jsonLines = input.messages.map((message) => JSON.stringify(message)).join("\n"); + + assert.throws( + () => build(input), + /not produced by the exact build invocation/u, + ); +}); + test("rejects debug-profile output even when the target name matches", () => { const input = fixture(); input.messages[1].profile.debug_assertions = true; @@ -294,7 +358,79 @@ test("rejects a release-path executable hardlinked to another build graph", () = assert.throws( () => build(input), - /regular, non-symlink, singly linked file/u, + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects a release-root executable hardlinked outside the release graph", () => { + const input = fixture(); + const external = path.join(input.root, "foreign", "codestory-cli.exe"); + fs.mkdirSync(path.dirname(external), { recursive: true }); + fs.linkSync(input.artifacts[0].executable, external); + + assert.throws( + () => build(input), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects a release-root executable hardlinked into another target graph", () => { + const input = fixture(); + const otherTarget = path.join( + input.targetDir, + "aarch64-pc-windows-msvc", + "release", + "deps", + "codestory-cli.exe", + ); + fs.mkdirSync(path.dirname(otherTarget), { recursive: true }); + fs.linkSync(input.artifacts[0].executable, otherTarget); + + assert.throws( + () => build(input), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects a non-executable hardlink posing as Cargo's release/deps peer", () => { + const input = fixture(); + addCargoBinPeer(input, "cli", "codestory-cli.pdb"); + + assert.throws( + () => build(input), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects an arbitrary executable name posing as Cargo's release/deps peer", () => { + const input = fixture(); + addCargoBinPeer(input, "cli", "evil.exe"); + + assert.throws( + () => build(input), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects the release-root spelling where Cargo uses a normalized deps peer", () => { + const input = fixture(); + addCargoBinPeer(input, "cli", "codestory-cli.exe"); + + assert.throws( + () => build(input), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects a hardlinked Cargo test executable", () => { + const input = fixture(); + const native = input.artifacts.find((artifact) => artifact.alias === "native_staging"); + const alias = path.join(input.releaseDir, "deps", "native_staging-copy.exe"); + fs.linkSync(native.executable, alias); + + assert.throws( + () => build(input), + /test executable has native aliases outside its Cargo output path/u, ); }); @@ -336,14 +472,86 @@ test("rejects a hardlink added after Cargo artifact selection", () => { assert.throws( () => - verifyCargoArtifactManifest({ - exactSha: SOURCE_SHA, - exactTree: SOURCE_TREE, - manifest, - rustTarget: RUST_TARGET, - workspaceRoot: input.root, - }), - /regular, non-symlink, singly linked file/u, + verify(input, manifest), + /not exactly the release-root executable and one release\/deps peer/u, + ); +}); + +test("rejects a third hardlink added after selecting Cargo's root and deps pair", () => { + const input = fixture(); + addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + fs.linkSync( + input.artifacts[0].executable, + path.join(input.root, "cross-graph-cli.exe"), + ); + + assert.throws( + () => verify(input, manifest), + /unsupported native hardlink count 3/u, + ); +}); + +test("rejects an identical-byte replacement of Cargo's recorded deps peer", () => { + const input = fixture(); + const peer = addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + fs.unlinkSync(peer); + fs.writeFileSync(peer, input.artifacts[0].contents); + + assert.throws( + () => verify(input, manifest), + /no longer matches its authenticated native links/u, + ); +}); + +test("rejects an identical-byte replacement of the selected release-root executable", () => { + const input = fixture(); + addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + fs.unlinkSync(input.artifacts[0].executable); + fs.writeFileSync(input.artifacts[0].executable, input.artifacts[0].contents); + + assert.throws( + () => verify(input, manifest), + /no longer matches its authenticated native links/u, + ); +}); + +test("rejects an identical-byte replacement of both recorded hardlink paths", () => { + const input = fixture(); + const selected = input.artifacts[0].executable; + const peer = addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + const replacementRoot = path.join(input.releaseDir, "replacement.exe"); + const replacementPeer = path.join(input.releaseDir, "deps", "replacement.exe"); + fs.writeFileSync(replacementRoot, input.artifacts[0].contents); + fs.linkSync(replacementRoot, replacementPeer); + const replacementIdentity = fs.lstatSync(replacementRoot, { bigint: true }); + assert.notEqual( + replacementIdentity.ino.toString(), + manifest.artifacts.cli.native_links.inode, + ); + fs.unlinkSync(selected); + fs.unlinkSync(peer); + fs.renameSync(replacementRoot, selected); + fs.renameSync(replacementPeer, peer); + + assert.throws( + () => verify(input, manifest), + /no longer matches its authenticated native links/u, + ); +}); + +test("rejects manifest native-link topology changed from deps to another graph", () => { + const input = fixture(); + addCargoBinPeer(input, "cli"); + const { manifest } = build(input); + manifest.artifacts.cli.native_links.paths[1] = "../debug/codestory-cli.exe"; + + assert.throws( + () => verify(input, manifest), + /native links cli values changed/u, ); });