Bound checked candidate metadata and restricted invocation resources
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
type CheckedWorld,
|
||||
} from "./artifacts.js";
|
||||
import { type ExecutionPlan, ExecutionPlanError } from "./plan.js";
|
||||
import type { CandidateMetadataScope } from "./metadata.js";
|
||||
|
||||
const maxFileBytes = 256 * 1024 * 1024;
|
||||
const metadataLimit = 16 * 1024 * 1024;
|
||||
@@ -21,7 +22,10 @@ const fail = (where: string, message: string): never => {
|
||||
};
|
||||
/** Roots must be trusted immutable output directories. Refuse symlinks, including
|
||||
* interior directory links. Hash verification is still required before serving bytes. */
|
||||
export async function artifactFileReader(roots: Readonly<Record<string, string>>): Promise<ArtifactReader> {
|
||||
export async function artifactFileReader(
|
||||
roots: Readonly<Record<string, string>>,
|
||||
metadata?: CandidateMetadataScope,
|
||||
): Promise<ArtifactReader> {
|
||||
const directories = new Map<string, string>();
|
||||
for (const [revision, root] of Object.entries(roots)) {
|
||||
if (!path.isAbsolute(root) || path.normalize(root) !== root || (await realpath(root)) !== root)
|
||||
@@ -47,6 +51,7 @@ export async function artifactFileReader(roots: Readonly<Record<string, string>>
|
||||
try {
|
||||
const stat = await handle.stat();
|
||||
if (!stat.isFile() || stat.size > limit) fail(relative, "File exceeds its declared bound");
|
||||
if (relative === "share/quixos/package-artifacts.json") metadata?.charge((stat.size + 1) * 32);
|
||||
// A fixed allocation and one extra byte detect growth without readFile's unbounded allocation.
|
||||
const buffer = Buffer.alloc(stat.size + 1);
|
||||
let offset = 0;
|
||||
@@ -72,8 +77,9 @@ export interface LoadedArtifactWorld {
|
||||
export async function loadArtifactWorld(
|
||||
plan: ExecutionPlan,
|
||||
roots: Readonly<Record<string, string>>,
|
||||
metadata?: CandidateMetadataScope,
|
||||
): Promise<LoadedArtifactWorld> {
|
||||
const read = await artifactFileReader(roots),
|
||||
const read = await artifactFileReader(roots, metadata),
|
||||
manifests: PackageArtifacts[] = [];
|
||||
const expected = plan.definition.packages.map((p) => p.revision).sort();
|
||||
if (JSON.stringify(Object.keys(roots).sort()) !== JSON.stringify(expected))
|
||||
|
||||
Reference in New Issue
Block a user