mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(managed): report ILONLY-clear assemblies as mixed in boundary summaries (#3516)
inspect_managed_native_boundaries derived summary.mixed_mode_or_native_header only from the managed native header and the native entry-point flag. A C++/CLI assembly with ILONLY clear and no ReadyToRun signature was classified as mixed-clr-native by inspect_managed_artifact but summarized as not mixed. Give the rule one owner, managedCliIsMixedClrNative in ManagedPeReader, and use it for both the artifact runtime family and the boundary summary. The summary now reads the CLI header directly instead of the cli_native projection. Fixes #3366 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
} from "../domain/managed/managedArtifact.js";
|
||||
import {
|
||||
managedCli32BitRequirement,
|
||||
managedCliIsMixedClrNative,
|
||||
readManagedPeLayout,
|
||||
type ManagedCliHeader,
|
||||
type ManagedPeLayout,
|
||||
@@ -164,7 +165,7 @@ const runtimeFamilyFromCliHeader = (
|
||||
): ManagedArtifactInspection["classification"]["runtime_family"] =>
|
||||
cli?.readyToRunSignature === true
|
||||
? "modern-dotnet"
|
||||
: cli !== null && (cli.flags & 1) === 0
|
||||
: cli !== null && managedCliIsMixedClrNative(cli)
|
||||
? "mixed-clr-native"
|
||||
: "unknown";
|
||||
|
||||
@@ -190,8 +191,6 @@ const runtimeFamilyFor = (
|
||||
);
|
||||
let runtimeFamily: ManagedArtifactInspection["classification"]["runtime_family"] =
|
||||
"unknown";
|
||||
// crossgen2 clears ILONLY on ReadyToRun output, so only a clear flag
|
||||
// without the ReadyToRun signature indicates mixed CLR/native code.
|
||||
if (runtimeFamilyFromCliHeader(cli) === "mixed-clr-native") {
|
||||
runtimeFamily = "mixed-clr-native";
|
||||
evidence.push({
|
||||
|
||||
@@ -20,6 +20,8 @@ import { managedTableRowCounts } from "./ManagedMetadataInventory.js";
|
||||
import type { ManagedMetadataInventory } from "./ManagedMetadataInventory.js";
|
||||
import {
|
||||
managedCli32BitRequirement,
|
||||
managedCliIsMixedClrNative,
|
||||
type ManagedCliHeader,
|
||||
type ManagedPeLayout,
|
||||
} from "./ManagedPeReader.js";
|
||||
import { readManagedValue } from "./ManagedReaderFailure.js";
|
||||
@@ -378,36 +380,37 @@ const boundaryKind = (
|
||||
};
|
||||
|
||||
export const cliNative = (
|
||||
pe: ManagedPeLayout,
|
||||
cli: ManagedCliHeader | null,
|
||||
): ManagedNativeBoundaryInspection["cli_native"] => {
|
||||
if (pe.cli === null) return null;
|
||||
if (cli === null) return null;
|
||||
return {
|
||||
il_only: (pe.cli.flags & 0x0000_0001) !== 0,
|
||||
requires_32bit: managedCli32BitRequirement(pe.cli.flags) === "required",
|
||||
strong_name_signed: (pe.cli.flags & 0x0000_0008) !== 0,
|
||||
native_entry_point: (pe.cli.flags & 0x0000_0010) !== 0,
|
||||
ready_to_run_signature: pe.cli.readyToRunSignature,
|
||||
managed_native_header_rva: pe.cli.managedNativeHeader.rva,
|
||||
managed_native_header_size: pe.cli.managedNativeHeader.size,
|
||||
il_only: (cli.flags & 0x0000_0001) !== 0,
|
||||
requires_32bit: managedCli32BitRequirement(cli.flags) === "required",
|
||||
strong_name_signed: (cli.flags & 0x0000_0008) !== 0,
|
||||
native_entry_point: (cli.flags & 0x0000_0010) !== 0,
|
||||
ready_to_run_signature: cli.readyToRunSignature,
|
||||
managed_native_header_rva: cli.managedNativeHeader.rva,
|
||||
managed_native_header_size: cli.managedNativeHeader.size,
|
||||
};
|
||||
};
|
||||
|
||||
/** Summarize declaration counts with the native facets of the CLI header. */
|
||||
export const nativeBoundarySummary = (
|
||||
native: ManagedNativeBoundaryInspection["cli_native"],
|
||||
cli: ManagedCliHeader | null,
|
||||
counts: Pick<
|
||||
ManagedNativeBoundaryInspection["summary"],
|
||||
"module_ref_count" | "pinvoke_import_count" | "native_implementation_count"
|
||||
>,
|
||||
): ManagedNativeBoundaryInspection["summary"] => ({
|
||||
...counts,
|
||||
ready_to_run: native?.ready_to_run_signature ?? null,
|
||||
ready_to_run: cli?.readyToRunSignature ?? null,
|
||||
mixed_mode_or_native_header:
|
||||
native === null
|
||||
cli === null
|
||||
? null
|
||||
: native.managed_native_header_rva !== 0 ||
|
||||
native.managed_native_header_size !== 0 ||
|
||||
native.native_entry_point,
|
||||
: managedCliIsMixedClrNative(cli) ||
|
||||
cli.managedNativeHeader.rva !== 0 ||
|
||||
cli.managedNativeHeader.size !== 0 ||
|
||||
(cli.flags & 0x0000_0010) !== 0,
|
||||
});
|
||||
|
||||
interface BoundaryInspectionContext {
|
||||
@@ -419,19 +422,18 @@ interface BoundaryInspectionContext {
|
||||
readonly moduleRefs: readonly ModuleRef[];
|
||||
readonly imports: readonly NativeImport[];
|
||||
readonly implementations: readonly NativeImplementation[];
|
||||
readonly native: ManagedNativeBoundaryInspection["cli_native"];
|
||||
readonly issues: readonly ManagedParseIssue[];
|
||||
}
|
||||
|
||||
export const buildNativeBoundaryInspection = ({
|
||||
target,
|
||||
bytes,
|
||||
pe,
|
||||
layout,
|
||||
inventory,
|
||||
moduleRefs,
|
||||
imports,
|
||||
implementations,
|
||||
native,
|
||||
issues,
|
||||
}: BoundaryInspectionContext): ManagedNativeBoundaryInspection =>
|
||||
managedNativeBoundaryInspectionSchema.parse(
|
||||
@@ -454,11 +456,11 @@ export const buildNativeBoundaryInspection = ({
|
||||
requires_artifact_sha256: target.sha256,
|
||||
requires_mvid: inventory.module?.mvid ?? null,
|
||||
},
|
||||
cli_native: native,
|
||||
cli_native: cliNative(pe.cli),
|
||||
module_refs: moduleRefs,
|
||||
pinvoke_imports: imports,
|
||||
native_implementations: implementations,
|
||||
summary: nativeBoundarySummary(native, {
|
||||
summary: nativeBoundarySummary(pe.cli, {
|
||||
module_ref_count: moduleRefs.length,
|
||||
pinvoke_import_count: imports.length,
|
||||
native_implementation_count: implementations.length,
|
||||
|
||||
@@ -76,6 +76,35 @@ describe("managed native boundaries", () => {
|
||||
"P/Invoke rows prove managed import declarations only; this inspection does not verify that a native library, export, thunk, or provider-qualified function exists.",
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "C++/CLI with a native method body",
|
||||
options: {
|
||||
cliFlags: 0,
|
||||
methods: [
|
||||
{ name: "NativeMethod", body: Buffer.from([0xc3]), implFlags: 1 },
|
||||
],
|
||||
},
|
||||
mixed: true,
|
||||
},
|
||||
{
|
||||
name: "ILONLY clear with unreadable metadata",
|
||||
options: { cliFlags: 0, corruptMetadataSignature: true },
|
||||
mixed: true,
|
||||
},
|
||||
{ name: "pure IL", options: {}, mixed: false },
|
||||
{ name: "ReadyToRun", options: { readyToRun: true }, mixed: true },
|
||||
])("agrees with artifact classification for $name", ({ options, mixed }) => {
|
||||
const bytes = buildManagedPeFixture(options);
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
const artifact = inspectManagedArtifactBytes(bytes, target);
|
||||
const boundaries = inspectManagedNativeBoundariesBytes(bytes, target);
|
||||
|
||||
expect(boundaries.summary.mixed_mode_or_native_header).toBe(mixed);
|
||||
if (artifact.classification.runtime_family === "mixed-clr-native")
|
||||
expect(boundaries.summary.mixed_mode_or_native_header).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed native boundary coded indexes", () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { readTypeMemberListFacts } from "./ManagedTypeMemberLists.js";
|
||||
import {
|
||||
readManagedPeLayout,
|
||||
type ManagedCliHeader,
|
||||
type ManagedPeLayout,
|
||||
} from "./ManagedPeReader.js";
|
||||
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
|
||||
@@ -33,8 +34,8 @@ import {
|
||||
type Inventory = ReturnType<typeof readManagedMetadataInventory>;
|
||||
|
||||
/**
|
||||
* Report a PE whose CLI metadata is absent or cannot be admitted. `native`
|
||||
* holds the CLI header facts when the header itself was admitted; otherwise
|
||||
* Report a PE whose CLI metadata is absent or cannot be admitted. `cli`
|
||||
* holds the CLI header when the header itself was admitted; otherwise
|
||||
* the header is absent (`not-managed`) or unreadable (`malformed`).
|
||||
*/
|
||||
const emptyInspection = (
|
||||
@@ -42,10 +43,10 @@ const emptyInspection = (
|
||||
bytes: Buffer,
|
||||
classification: "not-managed" | "malformed",
|
||||
{
|
||||
native = null,
|
||||
cli = null,
|
||||
issues = [],
|
||||
}: {
|
||||
readonly native?: ManagedNativeBoundaryInspection["cli_native"] | null;
|
||||
readonly cli?: ManagedCliHeader | null;
|
||||
readonly issues?: readonly ManagedParseIssue[];
|
||||
} = {},
|
||||
): ManagedNativeBoundaryInspection => {
|
||||
@@ -72,11 +73,11 @@ const emptyInspection = (
|
||||
requires_artifact_sha256: target.sha256,
|
||||
requires_mvid: null,
|
||||
},
|
||||
cli_native: native,
|
||||
cli_native: cliNative(cli),
|
||||
module_refs: [],
|
||||
pinvoke_imports: [],
|
||||
native_implementations: [],
|
||||
summary: nativeBoundarySummary(native, {
|
||||
summary: nativeBoundarySummary(cli, {
|
||||
module_ref_count: 0,
|
||||
pinvoke_import_count: 0,
|
||||
native_implementation_count: 0,
|
||||
@@ -141,7 +142,7 @@ export const inspectManagedNativeBoundariesBytes = (
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ManagedReaderFailure)) throw cause;
|
||||
return emptyInspection(target, bytes, "malformed", {
|
||||
native: cliNative(pe),
|
||||
cli: pe.cli,
|
||||
issues: [cause.issue],
|
||||
});
|
||||
}
|
||||
@@ -179,13 +180,12 @@ export const inspectManagedNativeBoundariesBytes = (
|
||||
moduleRefs,
|
||||
imports,
|
||||
implementations,
|
||||
native: cliNative(pe),
|
||||
issues,
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ManagedReaderFailure)) throw cause;
|
||||
return emptyInspection(target, bytes, "malformed", {
|
||||
native: cliNative(pe),
|
||||
cli: pe.cli,
|
||||
issues: [...issues, cause.issue],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +45,13 @@ export const managedCli32BitRequirement = (
|
||||
return pair === 0x0002_0002 ? "preferred" : "none";
|
||||
};
|
||||
|
||||
/**
|
||||
* crossgen2 clears ILONLY on ReadyToRun output, so only a clear ILONLY flag
|
||||
* without the ReadyToRun signature marks mixed CLR/native code.
|
||||
*/
|
||||
export const managedCliIsMixedClrNative = (cli: ManagedCliHeader): boolean =>
|
||||
(cli.flags & 0x0000_0001) === 0 && !cli.readyToRunSignature;
|
||||
|
||||
export interface ManagedPeLayout {
|
||||
readonly machine: number;
|
||||
readonly architecture: "x86" | "x86_64" | "arm" | "arm64";
|
||||
|
||||
Reference in New Issue
Block a user