fix(managed): preserve literal library names during native matching

This commit is contained in:
morluto committed 2026-10-09 21:01:52 +08:00
1 parent 5eb0049a9f
commit 81b70bc358
2 files changed
+176 -145

No files matched your search

@@ -109,9 +109,17 @@ const sameSymbolName = (expected: string, native: NativeSymbol): boolean =>
const normalizeModule = (value: string): string =>
basename(value)
.toLowerCase()
.replace(/^lib/u, "")
.replace(/\.(dll|dylib|so|node|exe)$/u, "");
const nativeModuleIdentities = (value: string): ReadonlySet<string> => {
const filename = basename(value).toLowerCase();
const stem = normalizeModule(filename);
const identities = new Set([stem]);
if (/\.(dylib|so)$/u.test(filename) && stem.startsWith("lib"))
identities.add(stem.slice("lib".length));
return identities;
};
const moduleCompatible = (
scope: string | null,
symbol: NativeSymbol,
@@ -120,8 +128,7 @@ const moduleCompatible = (
const expected = normalizeModule(scope);
return [symbol.module_name, symbol.module_path]
.filter((value): value is string => value !== null)
.map(normalizeModule)
.some((value) => value === expected);
.some((value) => nativeModuleIdentities(value).has(expected));
};
const candidateNames = (
@@ -16,7 +16,7 @@ const exampleInput = () =>
MANAGED_NATIVE_VERIFICATION_EXAMPLE,
);
const nativeEvidenceWithExports = (names: readonly string[]) => {
const nativeEvidenceWithExports = (names: readonly string[], path?: string) => {
const input = exampleInput();
const native = input.native_observations[0];
if (native === undefined || native.subject === null)
@@ -24,7 +24,7 @@ const nativeEvidenceWithExports = (names: readonly string[]) => {
const macho = inspectMachoSchema.parse(native.normalized_result);
return createEvidence(
{
path: native.subject.local_path,
path: path ?? native.subject.local_path,
sha256: native.subject.digest.sha256,
format: native.subject.format,
...(native.subject.architecture === null
@@ -60,6 +60,29 @@ const nativeEvidenceWithExports = (names: readonly string[]) => {
);
};
const inputWithImportScope = (scope: string) => {
const input = exampleInput();
const managed = input.managed_boundaries;
const normalized = managedNativeBoundaryInspectionSchema.parse(
managed.normalized_result,
);
return {
...input,
managed_boundaries: createEvidence(undefined, managed.provider, {
operation: managed.operation,
parameters: managed.parameters,
result: {
...normalized,
pinvoke_imports: normalized.pinvoke_imports.map((item) => ({
...item,
import_scope_name: scope,
})),
},
rawResult: null,
}),
};
};
describe("managed/native boundary Evidence identity", () => {
it("rejects managed boundary Evidence whose subject digest differs from its artifact", () => {
const input = exampleInput();
@@ -108,165 +131,166 @@ describe("managed/native boundary Evidence identity", () => {
});
});
describe("managed/native boundary verification", () => {
it("verifies a P/Invoke declaration against native export Evidence", () => {
const result = verifyManagedNativeBoundaries(exampleInput());
it("verifies a P/Invoke declaration against native export Evidence", () => {
const result = verifyManagedNativeBoundaries(exampleInput());
expect(result).toMatchObject({
algorithm: {
name: "rea-managed-native-verification",
token_to_address_mapping: "not-inferred",
},
managed_boundary: {
artifact_sha256: "6".repeat(64),
mvid: "00112233-4455-4677-8899-aabbccddeeff",
pinvoke_imports_total: 1,
},
native_observations: {
total: 1,
accepted: 1,
unsupported: 0,
symbols: 1,
},
summary: {
verified: 1,
inferred: 0,
unresolved: 0,
contradicted: 0,
},
pinvoke_imports: [
{
managed: {
import_name: "open_native",
import_scope_name: "nativehelper.dll",
declaration_verification: "managed-declaration-only",
},
status: "verified",
basis: "exact-export-name",
confidence: "observed",
matched_native: {
name: "open_native",
source: "macho-export",
},
expect(result).toMatchObject({
managed_boundary: {
artifact_sha256: "6".repeat(64),
mvid: "00112233-4455-4677-8899-aabbccddeeff",
pinvoke_imports_total: 1,
},
summary: { verified: 1 },
pinvoke_imports: [
{
managed: {
import_name: "open_native",
import_scope_name: "nativehelper.dll",
},
],
});
expect(result.verification_id).toMatch(/^mnv_[a-f0-9]{64}$/u);
expect(result.limitations.join(" ")).toContain(
"does not prove CLR binding",
);
});
it("reports module mismatch as a contradiction within supplied Evidence", () => {
const input = exampleInput();
const native = input.native_observations[0];
if (native === undefined || native.subject === null)
throw new Error("missing native example");
const mismatched = createEvidence(
{
path: "/examples/other.dll",
sha256: native.subject.digest.sha256,
format: "pe",
...(native.subject.architecture === null
? {}
: { architecture: native.subject.architecture }),
status: "verified",
basis: "exact-export-name",
confidence: "observed",
matched_native: { name: "open_native" },
},
native.provider,
{
operation: native.operation,
parameters: native.parameters,
result: native.normalized_result,
rawResult: native.raw_result,
confidence: native.confidence,
authority: native.authority,
environment: native.environment,
limitations: native.limitations,
locations: native.locations,
evidenceLinks: native.evidence_links,
},
);
],
});
});
const result = verifyManagedNativeBoundaries({
...input,
native_observations: [mismatched],
});
it("reports module mismatch as a contradiction within supplied Evidence", () => {
const input = exampleInput();
const native = input.native_observations[0];
if (native === undefined || native.subject === null)
throw new Error("missing native example");
const mismatched = createEvidence(
{
path: "/examples/other.dll",
sha256: native.subject.digest.sha256,
format: "pe",
...(native.subject.architecture === null
? {}
: { architecture: native.subject.architecture }),
},
native.provider,
{
operation: native.operation,
parameters: native.parameters,
result: native.normalized_result,
rawResult: native.raw_result,
confidence: native.confidence,
authority: native.authority,
environment: native.environment,
limitations: native.limitations,
locations: native.locations,
evidenceLinks: native.evidence_links,
},
);
expect(result.summary).toMatchObject({
verified: 0,
contradicted: 1,
});
expect(result.pinvoke_imports[0]).toMatchObject({
status: "contradicted",
basis: "module-mismatch",
});
const result = verifyManagedNativeBoundaries({
...input,
native_observations: [mismatched],
});
it("treats supported native Evidence with no symbols as unresolved", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [nativeEvidenceWithExports([])],
});
expect(result.summary).toMatchObject({
verified: 0,
contradicted: 1,
});
expect(result.pinvoke_imports[0]).toMatchObject({
status: "contradicted",
basis: "module-mismatch",
});
});
expect(result.native_observations).toMatchObject({
accepted: 1,
unsupported: 0,
symbols: 0,
});
expect(result.summary).toMatchObject({
verified: 0,
unresolved: 1,
});
expect(result.pinvoke_imports[0]).toMatchObject({
status: "unresolved",
basis: "no-native-candidate",
});
it("keeps literal module names distinct and accepts the shared-library lib alias", () => {
const literalName = verifyManagedNativeBoundaries({
...inputWithImportScope("library"),
native_observations: [
nativeEvidenceWithExports(["open_native"], "/examples/rary.dylib"),
],
});
expect(literalName.pinvoke_imports[0]).toMatchObject({
status: "contradicted",
basis: "module-mismatch",
matched_native: { module_path: "/examples/rary.dylib" },
});
it("does not verify a symbol whose spelling differs only by case", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [nativeEvidenceWithExports(["OPEN_NATIVE"])],
});
const sharedLibraryAlias = verifyManagedNativeBoundaries({
...inputWithImportScope("foo"),
native_observations: [
nativeEvidenceWithExports(["open_native"], "/examples/libfoo.dylib"),
],
});
expect(sharedLibraryAlias.pinvoke_imports[0]).toMatchObject({
status: "verified",
basis: "exact-export-name",
matched_native: { module_path: "/examples/libfoo.dylib" },
});
});
expect(result.summary).toMatchObject({ verified: 0, unresolved: 1 });
expect(result.pinvoke_imports[0]).toMatchObject({
status: "unresolved",
matched_native: null,
candidates: [],
});
it("treats supported native Evidence with no symbols as unresolved", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [nativeEvidenceWithExports([])],
});
it("retains every observed native candidate", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [
nativeEvidenceWithExports(["open_native", "_open_native"]),
],
});
expect(result.native_observations).toMatchObject({
accepted: 1,
unsupported: 0,
symbols: 0,
});
expect(result.summary).toMatchObject({
verified: 0,
unresolved: 1,
});
expect(result.pinvoke_imports[0]).toMatchObject({
status: "unresolved",
basis: "no-native-candidate",
});
});
expect(result.coverage).toMatchObject({
status: "complete-within-inputs",
});
expect(result.pinvoke_imports[0]?.candidates).toHaveLength(2);
it("does not verify a symbol whose spelling differs only by case", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [nativeEvidenceWithExports(["OPEN_NATIVE"])],
});
it("wraps verification in derived workflow Evidence", () => {
const evidence = verifyManagedNativeBoundariesEvidence(exampleInput());
if (!evidence.ok) throw evidence.error;
expect(evidence.value).toMatchObject({
operation: "verify_managed_native_boundaries",
provider: { id: "rea-dotnet-workflows" },
confidence: "inferred",
authority: "analyst-inference",
normalized_result: {
summary: { verified: 1 },
},
});
expect(evidence.value.evidence_links).toHaveLength(2);
expect(result.summary).toMatchObject({ verified: 0, unresolved: 1 });
expect(result.pinvoke_imports[0]).toMatchObject({
status: "unresolved",
matched_native: null,
candidates: [],
});
});
it("retains every observed native candidate", () => {
const result = verifyManagedNativeBoundaries({
...exampleInput(),
native_observations: [
nativeEvidenceWithExports(["open_native", "_open_native"]),
],
});
expect(result.coverage).toMatchObject({
status: "complete-within-inputs",
});
expect(result.pinvoke_imports[0]?.candidates).toHaveLength(2);
});
it("wraps verification in derived workflow Evidence", () => {
const evidence = verifyManagedNativeBoundariesEvidence(exampleInput());
if (!evidence.ok) throw evidence.error;
expect(evidence.value).toMatchObject({
operation: "verify_managed_native_boundaries",
provider: { id: "rea-dotnet-workflows" },
confidence: "inferred",
authority: "analyst-inference",
normalized_result: {
summary: { verified: 1 },
},
});
expect(evidence.value.evidence_links).toHaveLength(2);
});
describe("managed/native verification result algebra", () => {
it("rejects states without their required native match", () => {
const result = verifyManagedNativeBoundaries(exampleInput());