chore: integrate portable schemas and PID target contracts
Keep one producer-owned PID syscall catalog while incorporating the portable canonical-base64 schema fix. Consolidate equivalent resource cases around the stronger read/write, failed-attempt, self and distinct-PID controls. The integrated policy and base64 families pass 91 tests across four files. Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
This commit is contained in:
commit
1c73ea7e4e
7 files changed
+137
-32
No files matched your search
@@ -0,0 +1,71 @@
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import { expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { canonicalBase64Schema } from "./base64Schema.js";
|
||||
import { CANONICAL_BASE64_PATTERN } from "./stringPatterns.js";
|
||||
|
||||
it("keeps runtime and projected canonical base64 contracts equivalent", () => {
|
||||
const samples = [
|
||||
"",
|
||||
"AA==",
|
||||
"AAA=",
|
||||
"////",
|
||||
"AB==",
|
||||
"AAB=",
|
||||
"A",
|
||||
"AA",
|
||||
"AAA",
|
||||
"====",
|
||||
"AA==\n",
|
||||
"AA==\r\n",
|
||||
"AA==\u2028",
|
||||
"AA==\u2029",
|
||||
"AA\nAA",
|
||||
"AA=\n=",
|
||||
"AA-_",
|
||||
"a b c",
|
||||
42,
|
||||
null,
|
||||
];
|
||||
for (const io of ["input", "output"] as const) {
|
||||
const projected = z.toJSONSchema(canonicalBase64Schema, { io });
|
||||
const validate = new Ajv2020({ strict: true }).compile(projected);
|
||||
expect(JSON.stringify(projected)).not.toMatch(/\(\?<?[=!]/u);
|
||||
for (const sample of samples) {
|
||||
const expected =
|
||||
typeof sample === "string" && CANONICAL_BASE64_PATTERN.test(sample);
|
||||
expect(
|
||||
canonicalBase64Schema.safeParse(sample).success,
|
||||
String(sample),
|
||||
).toBe(expected);
|
||||
expect(validate(sample), JSON.stringify({ io, sample })).toBe(expected);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("validates large projected bodies without Unicode-set regexp stack exhaustion", () => {
|
||||
const encoded = Buffer.alloc(24 * 1024 * 1024 - 3, 0x81).toString("base64");
|
||||
const projected = z.toJSONSchema(canonicalBase64Schema);
|
||||
const validate = new Ajv2020({ strict: true }).compile(projected);
|
||||
const samples = [
|
||||
{ value: encoded, expected: true },
|
||||
{ value: `${encoded.slice(0, -1)}!`, expected: false },
|
||||
{ value: `${encoded.slice(0, -3)}R==`, expected: false },
|
||||
{ value: `${encoded}A`, expected: false },
|
||||
];
|
||||
for (const { value, expected } of samples) {
|
||||
expect(canonicalBase64Schema.safeParse(value).success).toBe(expected);
|
||||
expect(validate(value)).toBe(expected);
|
||||
for (const flag of ["", "u", "v"]) {
|
||||
const positive = new RegExp(projected.pattern!, flag);
|
||||
const negative = new RegExp(
|
||||
(projected.not as { pattern: string }).pattern,
|
||||
flag,
|
||||
);
|
||||
expect(positive.test(value) && !negative.test(value), flag).toBe(
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
BASE64_QUARTETS_PATTERN,
|
||||
CANONICAL_BASE64_INVALID_PATTERN,
|
||||
} from "./stringPatterns.js";
|
||||
|
||||
/** Exact canonical base64 using stack-bounded, portable JSON Schema patterns. */
|
||||
export const canonicalBase64Schema = z
|
||||
.string()
|
||||
.regex(BASE64_QUARTETS_PATTERN)
|
||||
.refine((value) => !CANONICAL_BASE64_INVALID_PATTERN.test(value), {
|
||||
message: "Expected canonical padded base64",
|
||||
})
|
||||
// JSON Schema negation expresses the same rejection without look-around.
|
||||
.meta({ not: { pattern: CANONICAL_BASE64_INVALID_PATTERN.source } });
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { CANONICAL_BASE64_PATTERN } from "../stringPatterns.js";
|
||||
import { canonicalBase64Schema } from "../base64Schema.js";
|
||||
|
||||
const sourceRangeSchema = z.strictObject({
|
||||
offset: z.number().int().nonnegative(),
|
||||
@@ -25,9 +25,7 @@ export const goModuleMetadataSchema = z.strictObject({
|
||||
dependencies: z.array(goModuleSchema),
|
||||
settings: z.array(z.strictObject({ key: z.string(), value: z.string() })),
|
||||
unparsed_lines: z.array(z.string()),
|
||||
unparsed_line_bytes_base64: z.array(
|
||||
z.string().regex(CANONICAL_BASE64_PATTERN),
|
||||
),
|
||||
unparsed_line_bytes_base64: z.array(canonicalBase64Schema),
|
||||
complete: z.boolean(),
|
||||
});
|
||||
|
||||
@@ -43,9 +41,9 @@ export const goBuildInfoSchema = z.strictObject({
|
||||
header_offset: z.number().int().nonnegative(),
|
||||
encoding: z.enum(["inline", "pointer"]),
|
||||
go_version: z.string().nullable(),
|
||||
go_version_bytes_base64: z.string().regex(CANONICAL_BASE64_PATTERN),
|
||||
go_version_bytes_base64: canonicalBase64Schema,
|
||||
module_text: z.string().nullable(),
|
||||
module_bytes_base64: z.string().regex(CANONICAL_BASE64_PATTERN),
|
||||
module_bytes_base64: canonicalBase64Schema,
|
||||
version_location: sourceRangeSchema,
|
||||
module_location: sourceRangeSchema,
|
||||
module: goModuleMetadataSchema,
|
||||
|
||||
@@ -2,16 +2,15 @@ import { z } from "zod";
|
||||
import { canBorrowParsedJsonSnapshot } from "../immutableJson.js";
|
||||
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { CANONICAL_BASE64_PATTERN } from "../stringPatterns.js";
|
||||
import { canonicalBase64Schema } from "../base64Schema.js";
|
||||
|
||||
const unsignedHex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
|
||||
const index = z.number().int().nonnegative();
|
||||
const scalar = z.union([z.string(), z.number().int()]);
|
||||
const range = z.strictObject({ offset: unsignedHex, bytes: unsignedHex });
|
||||
const canonicalBase64 = z.string().regex(CANONICAL_BASE64_PATTERN);
|
||||
const name = z.strictObject({
|
||||
display: z.string(),
|
||||
bytes_base64: canonicalBase64.nullable(),
|
||||
bytes_base64: canonicalBase64Schema.nullable(),
|
||||
location: range.nullable(),
|
||||
unknown_reason: z.string().nullable(),
|
||||
});
|
||||
@@ -151,7 +150,7 @@ const binaryLayoutObjectSchema = z.strictObject({
|
||||
z.strictObject({
|
||||
section_index: index,
|
||||
location: range,
|
||||
encoded_bytes_base64: canonicalBase64,
|
||||
encoded_bytes_base64: canonicalBase64Schema,
|
||||
entries: z.array(
|
||||
z.strictObject({ decoded_index: index, reported_offset: unsignedHex }),
|
||||
),
|
||||
|
||||
@@ -2,13 +2,12 @@ import { z } from "zod";
|
||||
|
||||
import type { BinaryArchitecture } from "../binaryTargetTypes.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { CANONICAL_BASE64_PATTERN } from "../stringPatterns.js";
|
||||
import { canonicalBase64Schema } from "../base64Schema.js";
|
||||
import { recordedNoteCoverageIssues } from "./recordedCrashNoteCoverage.js";
|
||||
|
||||
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
|
||||
const index = z.number().int().nonnegative();
|
||||
const signed32 = z.number().int().min(-2147483648).max(2147483647);
|
||||
const base64 = z.string().regex(CANONICAL_BASE64_PATTERN);
|
||||
const range = z.strictObject({ offset: hex, bytes: hex });
|
||||
const diagnostics = z.strictObject({
|
||||
stdout: z.string(),
|
||||
@@ -222,17 +221,17 @@ const recordedCrashObjectSchema = z.strictObject({
|
||||
type: z.union([z.string(), index]),
|
||||
location: range,
|
||||
owner_location: range,
|
||||
owner_bytes_base64: base64,
|
||||
owner_bytes_base64: canonicalBase64Schema,
|
||||
owner_display: z.string().nullable(),
|
||||
descriptor_location: range,
|
||||
descriptor_bytes_base64: base64,
|
||||
descriptor_bytes_base64: canonicalBase64Schema,
|
||||
}),
|
||||
),
|
||||
note_padding: z.array(
|
||||
z.strictObject({
|
||||
segment_index: index,
|
||||
location: range,
|
||||
bytes_base64: base64,
|
||||
bytes_base64: canonicalBase64Schema,
|
||||
}),
|
||||
),
|
||||
threads: z.array(
|
||||
|
||||
@@ -5,16 +5,25 @@ export const IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9._\x2d]*$/u;
|
||||
export const STABLE_IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9._:\x2f\x2d]*$/u;
|
||||
|
||||
// Repeated alphabet quartets exhaust V8's regexp stack on large bodies,
|
||||
// including under JSON Schema's Unicode-set mode. Check alphabet and terminal
|
||||
// padding separately, leaving an unconstrained fixed-width loop for length.
|
||||
const PADDED_BASE64_PREFIX = String.raw`^(?!.*[^A-Za-z0-9+\x2f=])(?!.*=[^=])(?!.*===)`;
|
||||
// especially under JSON Schema's Unicode-set mode. Check invalid content
|
||||
// separately from an unconstrained fixed-width loop for length.
|
||||
export const BASE64_QUARTETS_PATTERN = /^(?:....)*$/;
|
||||
const PADDED_BASE64_INVALID_PATTERN = /[^A-Za-z0-9+\x2f=]|=[^=]|===/;
|
||||
export const CANONICAL_BASE64_INVALID_PATTERN = new RegExp(
|
||||
`${PADDED_BASE64_INVALID_PATTERN.source}|[^AQgw]==$|[^AEIMQUYcgkosw048=]=$`,
|
||||
);
|
||||
|
||||
const excludingInvalidBase64 = (invalid: RegExp): RegExp =>
|
||||
new RegExp(
|
||||
`^(?!.*(?:${invalid.source}))${BASE64_QUARTETS_PATTERN.source.slice(1)}`,
|
||||
);
|
||||
|
||||
/** Padded base64 syntax, allowing nonzero pad bits and the empty encoding. */
|
||||
export const PADDED_BASE64_PATTERN = new RegExp(
|
||||
`${PADDED_BASE64_PREFIX}(?:....)*$`,
|
||||
export const PADDED_BASE64_PATTERN = excludingInvalidBase64(
|
||||
PADDED_BASE64_INVALID_PATTERN,
|
||||
);
|
||||
|
||||
/** Canonical padded base64, including zero pad bits and the empty encoding. */
|
||||
export const CANONICAL_BASE64_PATTERN = new RegExp(
|
||||
`${PADDED_BASE64_PREFIX}(?!.*[^AQgw]==$)(?!.*[^AEIMQUYcgkosw048=]=$)(?:....)*$`,
|
||||
export const CANONICAL_BASE64_PATTERN = excludingInvalidBase64(
|
||||
CANONICAL_BASE64_INVALID_PATTERN,
|
||||
);
|
||||
@@ -176,16 +176,6 @@ it.each([
|
||||
});
|
||||
|
||||
it.each([
|
||||
"prlimit64(900, RLIMIT_NOFILE, NULL, {rlim_cur=1024, rlim_max=4096}) = 0",
|
||||
"sched_getaffinity(900, 8, [0]) = 8",
|
||||
"sched_setaffinity(900, 8, [0]) = 0",
|
||||
"sched_getparam(900, {sched_priority=0}) = 0",
|
||||
"sched_setparam(900, {sched_priority=0}) = 0",
|
||||
"sched_getscheduler(900) = 0",
|
||||
"sched_setscheduler(900, SCHED_OTHER, {sched_priority=0}) = 0",
|
||||
"sched_getattr(900, {}, 56, 0) = 0",
|
||||
"sched_setattr(900, {}, 0) = 0",
|
||||
"sched_rr_get_interval(900, {tv_sec=0, tv_nsec=0}) = 0",
|
||||
"getpgid(900) = 900",
|
||||
"getsid(900) = 900",
|
||||
"get_robust_list(900, [0x1], [24]) = 0",
|
||||
@@ -238,3 +228,26 @@ it.each([
|
||||
])("retains unresolved target facts: %s", (line) => {
|
||||
expect(() => check(101, line)).toThrow("Unresolved process target");
|
||||
});
|
||||
|
||||
it.each([
|
||||
"prlimit64(900, RLIMIT_NOFILE, NULL, {rlim_cur=1024, rlim_max=1024}) = 0",
|
||||
"prlimit64(900, RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=1024}, NULL) = 0",
|
||||
"sched_setaffinity(900, 128, [0 1]) = 0",
|
||||
"sched_getaffinity(900, 128, [0 1]) = 8",
|
||||
"sched_setscheduler(900, SCHED_OTHER, {sched_priority=0}) = 0",
|
||||
"sched_getscheduler(900) = SCHED_OTHER",
|
||||
"sched_setparam(900, {sched_priority=0}) = 0",
|
||||
"sched_getparam(900, {sched_priority=0}) = 0",
|
||||
"sched_setattr(900, {size=56, sched_policy=SCHED_OTHER}, 0) = 0",
|
||||
"sched_getattr(900, {size=56, sched_policy=SCHED_OTHER}, 56, 0) = 0",
|
||||
"sched_rr_get_interval(900, {tv_sec=0, tv_nsec=0}) = 0",
|
||||
])("rejects historical PID resource and scheduler access: %s", (line) => {
|
||||
for (const live of [true, false]) {
|
||||
expect(() => check(101, line, live)).toThrow("non-inventory lookup");
|
||||
expect(() =>
|
||||
check(101, line.replace(/ = .*/u, " = -1 ESRCH (No such process)"), live),
|
||||
).toThrow("non-inventory lookup");
|
||||
}
|
||||
expect(check(101, line.replace("(900", "(9000"))).toEqual([]);
|
||||
expect(check(101, line.replace("(900", "(0"))).toEqual([]);
|
||||
});
|
||||
Reference in new issue
Block a user