rea-agents 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -650
- package/bridge/mitmproxy/capture.py +210 -0
- package/bridge/native/rea_lldb_tracer.py +303 -0
- package/bridge/process/ProcessRunTokenReader.swift +263 -0
- package/bridge/process/ProcessRunTokenReaderMain.swift +35 -0
- package/bridge/pwndbg/core_context.py +101 -0
- package/bridge/pwndbg/launch.py +39 -0
- package/bridge/pwndbg/launch_impl.py +34 -0
- package/bridge/pwntools/decoder_runtime.py +59 -0
- package/bridge/pwntools/layout.py +39 -0
- package/bridge/pwntools/layout_impl.py +309 -0
- package/bridge/pwntools/recorded_crash.py +39 -0
- package/bridge/pwntools/recorded_crash_impl.py +143 -0
- package/dist/android/JadxProvider.js +1 -0
- package/dist/application/CapabilityInventory.js +21 -0
- package/dist/application/DirectAnalysis.js +97 -23
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/ReconstructionObligationCandidates.js +5 -1
- package/dist/application/WebNetworkCapturePort.js +1 -0
- package/dist/application/WebNetworkCaptureService.js +132 -0
- package/dist/application/WindowsCapabilities.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +49 -6
- package/dist/application/binary/BinarySession.js +38 -10
- package/dist/application/binary/BinarySessionOpen.js +16 -3
- package/dist/application/binary/BinarySessionRecords.js +20 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutPort.js +1 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +57 -0
- package/dist/application/binaryDiagnostics/RecordedCrashPort.js +1 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +60 -0
- package/dist/application/evm/EvmInterfacePort.js +1 -0
- package/dist/application/evm/EvmInterfaceService.js +62 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +15 -2
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +42 -23
- package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +26 -2
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +74 -24
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +23 -10
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +145 -70
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +14 -15
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +41 -1
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -9
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +118 -12
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +5 -5
- package/dist/application/{ProcessCli.js → process/ProcessCli.js} +10 -10
- package/dist/application/{ProcessEvidence.js → process/ProcessEvidence.js} +4 -4
- package/dist/artifacts/ArtifactProvider.js +30 -3
- package/dist/artifacts/ArtifactProviderMetadata.js +2 -1
- package/dist/artifacts/ZipArtifactReader.js +3 -0
- package/dist/artifacts/apple/DylibResolutionReader.js +259 -0
- package/dist/artifacts/apple/MachoLoadCommandReader.js +294 -0
- package/dist/{application → artifacts/extraction}/ArtifactExtraction.js +10 -10
- package/dist/{application → artifacts/inventory}/ArtifactGraphConstruction.js +3 -3
- package/dist/{application → artifacts/inventory}/ArtifactInventory.js +4 -4
- package/dist/{application/ArtifactInventory → artifacts/inventory}/classify.js +2 -2
- package/dist/{application/ArtifactInventory → artifacts/inventory}/reader.js +5 -5
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanCanonical.js +3 -3
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanReader.js +5 -5
- package/dist/artifacts/readStableArtifact.js +20 -4
- package/dist/browser/CdpCaptureEventHandlers.js +90 -2
- package/dist/browser/CdpCaptureEvents.js +9 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +12 -3
- package/dist/browser/history/CaptureFailures.js +101 -0
- package/dist/browser/history/CaptureFormatError.js +10 -0
- package/dist/browser/history/CaptureRedaction.js +65 -0
- package/dist/browser/history/CaptureRelease.js +12 -0
- package/dist/browser/history/HarCapture.js +165 -0
- package/dist/browser/history/HarCaptureAdapter.js +20 -0
- package/dist/browser/history/HarCaptureProcess.js +57 -0
- package/dist/browser/history/HarJson.js +91 -0
- package/dist/browser/history/HarSchema.js +72 -0
- package/dist/browser/history/HistoricalCaptureDecoder.js +186 -0
- package/dist/browser/history/HistoricalCaptureFormatAdapter.js +1 -0
- package/dist/browser/history/MitmproxyCaptureAdapter.js +42 -0
- package/dist/cli/artifactCommands.js +26 -1
- package/dist/cli/binaryDiagnosticsCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +8 -8
- package/dist/cli/electronCommands.js +16 -9
- package/dist/cli/evmCommands.js +24 -0
- package/dist/cli/firmwareCommands.js +6 -3
- package/dist/cli/javascriptApplicationAnalysis.js +16 -3
- package/dist/cli/jsonOutput.js +133 -0
- package/dist/cli/nativeCallCommands.js +35 -0
- package/dist/cli/processCommands.js +1 -1
- package/dist/cli/streamedJsonOutput.js +56 -0
- package/dist/cli/utilityCommands.js +2 -0
- package/dist/cli/webNetworkCaptureCommands.js +40 -0
- package/dist/cli.js +9 -3
- package/dist/cliBrowserScenarioCommands.js +2 -2
- package/dist/cliCommandNames.js +6 -0
- package/dist/cliJsonInput.js +30 -1
- package/dist/cliOutput.js +21 -6
- package/dist/composition/binaryDiagnostics.js +8 -0
- package/dist/composition/evm.js +4 -0
- package/dist/composition/webNetworkCaptures.js +6 -0
- package/dist/contracts/artifactToolContracts.js +6 -0
- package/dist/contracts/browserCaptureToolInputSchema.js +43 -0
- package/dist/contracts/browserToolContracts.js +4 -3
- package/dist/contracts/errorSchemas.js +8 -1
- package/dist/contracts/evm/evmToolContracts.js +20 -0
- package/dist/contracts/investigationExamples.js +2 -2
- package/dist/contracts/managed/managedToolContracts.js +7 -3
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +42 -0
- package/dist/contracts/native/nativeToolContracts.js +17 -1
- package/dist/contracts/officialToolContracts.js +12 -9
- package/dist/contracts/process/processCaptureExample.js +2 -0
- package/dist/contracts/sessionLifecycleInputs.js +22 -5
- package/dist/contracts/sessionStatusContract.js +10 -2
- package/dist/contracts/sessionToolContracts.js +8 -1
- package/dist/contracts/sessionToolSchemas.js +10 -3
- package/dist/contracts/toolContractExamples.js +1 -1
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +30 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -1
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -1
- package/dist/contracts/webNetworkCaptureToolContracts.js +24 -0
- package/dist/domain/analysisErrorBase.js +15 -0
- package/dist/domain/analysisErrorCore.js +59 -5
- package/dist/domain/analysisErrorPresentation.js +29 -1
- package/dist/domain/analysisErrorProjection.js +52 -2
- package/dist/domain/analysisSnapshot.js +166 -2
- package/dist/domain/apple/dyldPaths.js +81 -0
- package/dist/domain/apple/dylibResolution.js +367 -0
- package/dist/domain/apple/dylibResolutionFindings.js +75 -0
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/browserObservation.js +1 -1
- package/dist/domain/browserObservationSchemas.js +13 -0
- package/dist/domain/browserScenarioValues.js +9 -2
- package/dist/domain/callPathSchemas.js +1 -1
- package/dist/domain/canonicalDigest.js +13 -1
- package/dist/domain/changedBehavior.js +3 -3
- package/dist/domain/comparisonStatus.js +2 -30
- package/dist/domain/conformanceTrustGate.js +0 -16
- package/dist/domain/evidence.js +17 -5
- package/dist/domain/evm/evmInterface.js +63 -0
- package/dist/domain/explicitSensitiveFailure.js +92 -0
- package/dist/domain/explicitSensitiveValues.js +10 -0
- package/dist/domain/firmware/firmwareAnalysis.js +8 -1
- package/dist/domain/javascript/electronActiveObservation.js +9 -4
- package/dist/domain/javascript/htmlArtifactReferences.js +40 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +21 -5
- package/dist/domain/javascript/javascriptExportShapeVariants.js +0 -10
- package/dist/domain/javascript/javascriptSemanticGraph.js +21 -5
- package/dist/domain/jsonValue.js +4 -0
- package/dist/domain/localPath.js +2 -0
- package/dist/domain/native/binaryLayout.js +357 -0
- package/dist/domain/native/nativeCallObservation.js +164 -0
- package/dist/domain/native/objcSwiftMetadata.js +0 -39
- package/dist/domain/native/recordedCrash.js +266 -0
- package/dist/domain/native/recordedCrashNoteCoverage.js +73 -0
- package/dist/domain/{processCapture.js → process/processCapture.js} +139 -2
- package/dist/domain/{processCaptureExample.js → process/processCaptureExample.js} +5 -0
- package/dist/domain/process/processCaptureParsing.js +54 -0
- package/dist/domain/{processCaptureValidation.js → process/processCaptureValidation.js} +18 -2
- package/dist/domain/{processComparison.js → process/processComparison.js} +1 -1
- package/dist/domain/{processObservation.js → process/processObservation.js} +1 -1
- package/dist/domain/{processTraceSpecification.js → process/processTraceSpecification.js} +1 -1
- package/dist/domain/providerSelectionError.js +4 -2
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/reconstructionVerificationSchemas.js +1 -1
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +3 -3
- package/dist/domain/webCaptureDiff.js +1 -0
- package/dist/domain/webNetworkCapture.js +118 -0
- package/dist/domain/webNetworkCaptureCoordinates.js +58 -0
- package/dist/dotnet/ManagedExceptionRegionValidation.js +41 -0
- package/dist/dotnet/ManagedMemberInspector.js +4 -2
- package/dist/dotnet/ManagedMemberInspectorCore.js +38 -32
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +23 -7
- package/dist/dotnet/ManagedMemberRows.js +14 -3
- package/dist/dotnet/ManagedMetadataHeaps.js +20 -2
- package/dist/dotnet/ManagedMetadataInventory.js +38 -28
- package/dist/dotnet/ManagedMetadataInventoryRows.js +24 -10
- package/dist/dotnet/ManagedMethodBodyReader.js +7 -1
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +15 -3
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +3 -1
- package/dist/dotnet/ManagedPeReader.js +11 -0
- package/dist/evm/EvmBytecodeCarrier.js +21 -0
- package/dist/evm/EvmWorkerLimits.js +40 -0
- package/dist/evm/EvmoleFailures.js +126 -0
- package/dist/evm/EvmoleInterfaceAdapter.js +53 -0
- package/dist/evm/EvmoleInterfaceProvider.js +273 -0
- package/dist/evm/EvmoleInterfaceWorker.js +87 -0
- package/dist/evm/EvmoleRelease.js +16 -0
- package/dist/firmware/FirmwareCommand.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraLauncher.js +1 -0
- package/dist/ghidra/GhidraProvider.js +2 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +1 -1
- package/dist/hopper/BridgeLauncher.js +2 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +4 -2
- package/dist/javascript/recovery/WakaruCommand.js +3 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +1 -0
- package/dist/native/CommandRunner.js +2 -1
- package/dist/native/LldbCallTracer.js +278 -0
- package/dist/native/NativeCallObservation.js +135 -0
- package/dist/native/NativeMacOSProvider.js +19 -3
- package/dist/native/NativeMacOSProviderMetadata.js +8 -3
- package/dist/native/pwndbg/PwndbgCoreContext.js +160 -0
- package/dist/native/pwntools/PwntoolsDecoder.js +203 -0
- package/dist/native/pwntools/PwntoolsFailures.js +180 -0
- package/dist/native/pwntools/PwntoolsLayoutProvider.js +36 -0
- package/dist/native/pwntools/PwntoolsRecordedCrashProvider.js +75 -0
- package/dist/native/pwntools/PwntoolsRelease.js +19 -0
- package/dist/native/pwntools/PwntoolsResourceLimits.js +76 -0
- package/dist/native/pwntools/RecordedCrashSourceBindings.js +46 -0
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +101 -0
- package/dist/process/DarwinProcessRunTokenReader.js +294 -0
- package/dist/process/OwnedCommand.js +113 -0
- package/dist/process/ProcessOwnership.js +263 -33
- package/dist/process/ProcessOwnershipObservation.js +161 -24
- package/dist/process/ProviderProcess.js +30 -3
- package/dist/{application → process/capture}/ProcessCaptureCapability.js +15 -0
- package/dist/{application → process/capture}/ProcessCaptureError.js +17 -1
- package/dist/process/capture/ProcessCaptureLifecycle.js +615 -0
- package/dist/{application → process/capture}/ProcessHarness.js +195 -29
- package/dist/{application → process/capture}/ProcessSampling.js +2 -2
- package/dist/server/createServer.js +22 -0
- package/dist/server/promptCompletion.js +10 -3
- package/dist/server/registerBinaryDiagnosticsTools.js +17 -0
- package/dist/server/registerEvidenceTools.js +1 -1
- package/dist/server/registerEvmTools.js +17 -0
- package/dist/server/registerProcessComparisonTool.js +1 -1
- package/dist/server/registerRecordedCrashTools.js +17 -0
- package/dist/server/registerSessionTools.js +2 -2
- package/dist/server/registerWebNetworkCaptureTool.js +28 -0
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionToolPolicies.js +1 -1
- package/dist/server/toolResult.js +1 -1
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +37 -9
- package/scripts/rea.mjs +11 -3
- package/skills/reverse-engineer-anything/SKILL.md +23 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +29 -0
- package/third_party/README.md +60 -0
- package/third_party/evmole/LICENSE +21 -0
- package/third_party/evmole/README.md +22 -0
- package/third_party/pwndbg/LICENSE.md +21 -0
- package/third_party/pwndbg/README.md +23 -0
- package/third_party/pwntools/README.md +40 -0
- package/dist/application/AuthorizedArtifactInventory.js +0 -7
- package/dist/application/ProcessCaptureLifecycle.js +0 -299
- package/dist/application/StoreFileLock.js +0 -124
- package/dist/contracts/processCaptureExample.js +0 -2
- package/dist/domain/bytecodeProvider.js +0 -178
- package/dist/domain/customProtocolCapture.js +0 -137
- package/dist/domain/eventProcessTree.js +0 -225
- package/dist/domain/mobileApplicationInvestigation.js +0 -159
- package/dist/domain/packageAnalysis.js +0 -123
- package/dist/domain/peInspection.js +0 -197
- package/dist/domain/processCaptureParsing.js +0 -21
- package/dist/domain/protocolCapture.js +0 -217
- package/dist/domain/runtimeIdentification.js +0 -212
- package/dist/generatedMcpToolCatalog.js +0 -37
- /package/dist/application/{ArtifactExtractionDestination.js → artifacts/ArtifactExtractionDestination.js} +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/policy.js +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/types.js +0 -0
- /package/dist/domain/{processEvidenceProvider.js → process/processEvidenceProvider.js} +0 -0
- /package/dist/domain/{processScenario.js → process/processScenario.js} +0 -0
- /package/dist/domain/{processTraceComparison.js → process/processTraceComparison.js} +0 -0
- /package/dist/domain/{processTraceDimensionProjection.js → process/processTraceDimensionProjection.js} +0 -0
- /package/dist/domain/{processTraceEvaluation.js → process/processTraceEvaluation.js} +0 -0
- /package/dist/{application → process/capture}/FilesystemSnapshot.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureEnvironment.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureJournal.js +0 -0
- /package/dist/{application → process/capture}/ProcessFilesystemEffects.js +0 -0
- /package/dist/{application → process/capture}/ProcessNormalization.js +0 -0
- /package/dist/{application → process/capture}/ProcessScenarioRuntimeValidation.js +0 -0
- /package/dist/{application → process/capture}/ProcessTimer.js +0 -0
- /package/dist/{application → process/capture}/TerminalRenderer.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createJavaScriptSemanticGraphUnknown } from "../../domain/javascript/javascriptSemanticGraph.js";
|
|
2
|
-
import {
|
|
2
|
+
import { retainSemanticGraphNode, addSemanticGraphRelation, addSemanticGraphUnknown, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
3
3
|
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
|
4
4
|
import { semanticPropertySlot } from "./JavaScriptSemanticGraphValueProjection.js";
|
|
5
5
|
/** Project static object reads, writes, spreads, and destructuring. */
|
|
@@ -45,7 +45,7 @@ export const projectSemanticObjects = (context) => {
|
|
|
45
45
|
addObjectUnknown(context, operation, slot ?? occurrence);
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
-
const addObjectOccurrence = (context, operation) =>
|
|
48
|
+
const addObjectOccurrence = (context, operation) => retainSemanticGraphNode(context.state, context.file, {
|
|
49
49
|
kind: "expression",
|
|
50
50
|
roleKey: operation.objectOperationId,
|
|
51
51
|
location: operation.location,
|
|
@@ -60,7 +60,7 @@ const addObjectOccurrence = (context, operation) => addSemanticGraphNode(context
|
|
|
60
60
|
operation_kind: operation.kind,
|
|
61
61
|
property_name: operation.propertyName,
|
|
62
62
|
},
|
|
63
|
-
}
|
|
63
|
+
});
|
|
64
64
|
const addObjectUnknown = (context, operation, node) => {
|
|
65
65
|
const relation = operation.kind === "read"
|
|
66
66
|
? "reads-property"
|
|
@@ -1,18 +1,124 @@
|
|
|
1
1
|
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
|
|
2
2
|
import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
|
3
|
-
/**
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/** Index one immutable file IR; read retained callable membership at query time. */
|
|
4
|
+
export const createSemanticCallableOwnerLookup = (ir, nodes) => {
|
|
5
|
+
const entries = ir.callables
|
|
6
|
+
.map((callable, order) => ({
|
|
7
|
+
callableId: callable.callableId,
|
|
8
|
+
range: callable.location,
|
|
9
|
+
order,
|
|
10
|
+
priority: positionKey(callable.location),
|
|
11
|
+
}))
|
|
12
|
+
.sort((left, right) => pointCompare(left.range.start, right.range.start) ||
|
|
13
|
+
left.order - right.order);
|
|
14
|
+
const tree = buildSourceIntervalTree(entries, 0, entries.length);
|
|
15
|
+
return (location) => {
|
|
16
|
+
if (location === null || tree === null || nodes.size === 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
const pending = [tree];
|
|
19
|
+
let best;
|
|
20
|
+
let result;
|
|
21
|
+
while (pending.length > 0) {
|
|
22
|
+
const current = pending.pop();
|
|
23
|
+
if (current === undefined ||
|
|
24
|
+
pointCompare(current.minimumStart, location.start) > 0 ||
|
|
25
|
+
pointCompare(current.maximumEnd, location.end) < 0)
|
|
26
|
+
continue;
|
|
27
|
+
const candidate = current.entry;
|
|
28
|
+
if (contains(candidate.range, location)) {
|
|
29
|
+
const node = nodes.get(candidate.callableId);
|
|
30
|
+
const rank = best === undefined
|
|
31
|
+
? 1
|
|
32
|
+
: compareCodePoints(candidate.priority, best.priority) ||
|
|
33
|
+
best.order - candidate.order;
|
|
34
|
+
if (node !== undefined && rank > 0) {
|
|
35
|
+
best = candidate;
|
|
36
|
+
result = node;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (current.left !== null)
|
|
40
|
+
pending.push(current.left);
|
|
41
|
+
if (current.right !== null)
|
|
42
|
+
pending.push(current.right);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
12
46
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
47
|
+
const buildSourceIntervalTree = (entries, start, end) => {
|
|
48
|
+
if (start >= end)
|
|
49
|
+
return null;
|
|
50
|
+
const middle = Math.floor((start + end) / 2);
|
|
51
|
+
const entry = entries[middle];
|
|
52
|
+
if (entry === undefined)
|
|
53
|
+
return null;
|
|
54
|
+
// This recursion follows a balanced index, not the producer's nesting depth.
|
|
55
|
+
const left = buildSourceIntervalTree(entries, start, middle);
|
|
56
|
+
const right = buildSourceIntervalTree(entries, middle + 1, end);
|
|
57
|
+
let minimumEnd = entry.range.end;
|
|
58
|
+
let maximumEnd = entry.range.end;
|
|
59
|
+
for (const child of [left, right]) {
|
|
60
|
+
if (child !== null && pointCompare(child.minimumEnd, minimumEnd) < 0)
|
|
61
|
+
minimumEnd = child.minimumEnd;
|
|
62
|
+
if (child !== null && pointCompare(child.maximumEnd, maximumEnd) > 0)
|
|
63
|
+
maximumEnd = child.maximumEnd;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
entry,
|
|
67
|
+
minimumStart: left?.minimumStart ?? entry.range.start,
|
|
68
|
+
maximumStart: right?.maximumStart ?? entry.range.start,
|
|
69
|
+
minimumEnd,
|
|
70
|
+
maximumEnd,
|
|
71
|
+
left,
|
|
72
|
+
right,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/** Index an immutable node list; return contained ranges in producer order. */
|
|
76
|
+
export const createSemanticNodeRangeLookup = (nodes) => {
|
|
77
|
+
const entries = nodes
|
|
78
|
+
.flatMap((node, order) => {
|
|
79
|
+
const range = node.identity.source_range;
|
|
80
|
+
return range === null ? [] : [{ node, range, order }];
|
|
81
|
+
})
|
|
82
|
+
.sort((left, right) => pointCompare(left.range.start, right.range.start) ||
|
|
83
|
+
left.order - right.order);
|
|
84
|
+
const tree = buildSourceIntervalTree(entries, 0, entries.length);
|
|
85
|
+
return (range) => {
|
|
86
|
+
if (tree === null)
|
|
87
|
+
return [];
|
|
88
|
+
const pending = [tree];
|
|
89
|
+
const matches = [];
|
|
90
|
+
while (pending.length > 0) {
|
|
91
|
+
const current = pending.pop();
|
|
92
|
+
if (current === undefined ||
|
|
93
|
+
pointCompare(current.maximumStart, range.start) < 0 ||
|
|
94
|
+
pointCompare(current.minimumEnd, range.end) > 0)
|
|
95
|
+
continue;
|
|
96
|
+
if (contains(range, current.entry.range))
|
|
97
|
+
matches.push(current.entry);
|
|
98
|
+
if (current.left !== null)
|
|
99
|
+
pending.push(current.left);
|
|
100
|
+
if (current.right !== null)
|
|
101
|
+
pending.push(current.right);
|
|
102
|
+
}
|
|
103
|
+
return matches
|
|
104
|
+
.sort((left, right) => left.order - right.order)
|
|
105
|
+
.map(({ node }) => node);
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
/** Index exact call ranges, preserving the first producer occurrence. */
|
|
109
|
+
export const createSemanticCallSiteLookup = (ir, nodes) => {
|
|
110
|
+
const identifiers = new Map();
|
|
111
|
+
for (const call of ir.callSites) {
|
|
112
|
+
const key = rangeKey(call.location);
|
|
113
|
+
if (!identifiers.has(key))
|
|
114
|
+
identifiers.set(key, call.callSiteId);
|
|
115
|
+
}
|
|
116
|
+
return (range) => {
|
|
117
|
+
const identifier = identifiers.get(rangeKey(range));
|
|
118
|
+
return identifier === undefined ? undefined : nodes.get(identifier);
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const rangeKey = (range) => `${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`;
|
|
16
122
|
/** Report extractor support without treating missing families as absence. */
|
|
17
123
|
export const semanticFamilyStatus = (family, analysis) => {
|
|
18
124
|
if (![
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createJavaScriptSemanticGraphUnknown } from "../../domain/javascript/javascriptSemanticGraph.js";
|
|
2
|
-
import {
|
|
2
|
+
import { retainSemanticGraphNode, addSemanticGraphRelation, addSemanticGraphUnknown, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
3
3
|
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
|
4
4
|
import { semanticCallSiteAt, } from "./JavaScriptSemanticGraphFlowProjection.js";
|
|
5
5
|
/** Project built-in resource acquisition and exact local release handles. */
|
|
@@ -21,7 +21,7 @@ export const projectSemanticResources = (context) => {
|
|
|
21
21
|
else
|
|
22
22
|
projectRelease(context, operation, resourceNodes);
|
|
23
23
|
};
|
|
24
|
-
const addResourceNode = (context, operation) =>
|
|
24
|
+
const addResourceNode = (context, operation) => retainSemanticGraphNode(context.state, context.file, {
|
|
25
25
|
kind: "resource",
|
|
26
26
|
roleKey: operation.resourceId,
|
|
27
27
|
location: operation.location,
|
|
@@ -31,7 +31,7 @@ const addResourceNode = (context, operation) => addSemanticGraphNode(context.sta
|
|
|
31
31
|
: (context.callableNodes.get(operation.ownerCallableId)?.node_id ??
|
|
32
32
|
null),
|
|
33
33
|
properties: { method: operation.method },
|
|
34
|
-
}
|
|
34
|
+
});
|
|
35
35
|
const projectRelease = (context, operation, resourceNodes) => {
|
|
36
36
|
const callSite = semanticCallSiteAt(context, operation.location);
|
|
37
37
|
const resources = operation.linkedResourceIds.flatMap((identifier) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { retainSemanticGraphNode, addSemanticGraphRelation, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
2
2
|
/** Project bounded literal values and object slots for exact query seeds. */
|
|
3
3
|
export const projectSemanticValues = (context) => {
|
|
4
4
|
for (const binding of context.ir.bindings) {
|
|
@@ -55,16 +55,16 @@ const projectValue = (input) => {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
-
const addLiteralNode = (context, binding, value, role) =>
|
|
58
|
+
const addLiteralNode = (context, binding, value, role) => retainSemanticGraphNode(context.state, context.file, {
|
|
59
59
|
kind: "literal",
|
|
60
60
|
roleKey: `literal:${binding.bindingId}:${role}:${JSON.stringify(value)}`,
|
|
61
61
|
location: binding.definitions[0]?.location ?? null,
|
|
62
62
|
label: JSON.stringify(value),
|
|
63
63
|
functionNodeId: context.bindingNodes.get(binding.bindingId)?.function_node_id ?? null,
|
|
64
64
|
properties: { value },
|
|
65
|
-
}
|
|
65
|
+
});
|
|
66
66
|
/** Create one canonical property slot for a binding/property identity. */
|
|
67
|
-
export const semanticPropertySlot = (context, objectBindingId, name) =>
|
|
67
|
+
export const semanticPropertySlot = (context, objectBindingId, name) => retainSemanticGraphNode(context.state, context.file, {
|
|
68
68
|
kind: "property-slot",
|
|
69
69
|
roleKey: `property:${objectBindingId}:${name}`,
|
|
70
70
|
location: null,
|
|
@@ -74,4 +74,4 @@ export const semanticPropertySlot = (context, objectBindingId, name) => addSeman
|
|
|
74
74
|
name,
|
|
75
75
|
object_binding_id: objectBindingId,
|
|
76
76
|
},
|
|
77
|
-
}
|
|
77
|
+
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { AnalysisError } from "
|
|
3
|
-
import { AnalysisInputError } from "
|
|
4
|
-
import { projectAnalysisError } from "
|
|
5
|
-
import { createEvidence, parseEvidence } from "
|
|
6
|
-
import { jsonValueSchema } from "
|
|
7
|
-
import { projectInputIssues } from "
|
|
8
|
-
import { processTraceSpecificationSchema } from "
|
|
9
|
-
import { processScenarioSchema } from "
|
|
10
|
-
import { compareProcessCaptures, parseProcessCapture, } from "
|
|
11
|
-
import { captureProcessScenario } from "
|
|
2
|
+
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
|
3
|
+
import { AnalysisInputError } from "../../domain/analysisErrorCore.js";
|
|
4
|
+
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
|
5
|
+
import { createEvidence, parseEvidence } from "../../domain/evidence.js";
|
|
6
|
+
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
|
7
|
+
import { projectInputIssues } from "../../domain/inputIssueProjection.js";
|
|
8
|
+
import { processTraceSpecificationSchema } from "../../domain/process/processTraceComparison.js";
|
|
9
|
+
import { processScenarioSchema } from "../../domain/process/processScenario.js";
|
|
10
|
+
import { compareProcessCaptures, parseProcessCapture, } from "../../domain/process/processCapture.js";
|
|
11
|
+
import { captureProcessScenario } from "../../process/capture/ProcessHarness.js";
|
|
12
12
|
import { PROCESS_PROVIDER, createProcessCaptureEvidence, } from "./ProcessEvidence.js";
|
|
13
13
|
/** Identify the process workflow's typed diagnostic result for CLI exit status. */
|
|
14
14
|
export const isProcessCliFailure = (value) => typeof value === "object" &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createEvidence } from "
|
|
2
|
-
import { PROCESS_PROVIDER } from "
|
|
3
|
-
import { jsonValueSchema } from "
|
|
4
|
-
export { PROCESS_PROVIDER } from "
|
|
1
|
+
import { createEvidence } from "../../domain/evidence.js";
|
|
2
|
+
import { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js";
|
|
3
|
+
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
|
4
|
+
export { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js";
|
|
5
5
|
/** Project one process scenario into secret-free Evidence parameters. */
|
|
6
6
|
const processEvidenceParameters = (scenario) => ({
|
|
7
7
|
executable_name: scenario.executable.split("/").at(-1) ?? scenario.executable,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
|
|
2
2
|
import { inspectBundleKeyedArchive } from "./apple/KeyedArchiveReader.js";
|
|
3
|
+
import { traceDylibResolution } from "./apple/DylibResolutionReader.js";
|
|
3
4
|
import { basename, dirname } from "node:path";
|
|
4
|
-
import { inventoryArtifact } from "
|
|
5
|
-
import { extractArtifact } from "
|
|
5
|
+
import { inventoryArtifact } from "./inventory/ArtifactInventory.js";
|
|
6
|
+
import { extractArtifact } from "./extraction/ArtifactExtraction.js";
|
|
6
7
|
import { analyzeInterfaceBuilderBundle } from "./apple/InterfaceBuilderAnalysis.js";
|
|
7
8
|
import { analyzeAppleAssetCatalogs } from "./apple/AppleAssetCatalogAnalysis.js";
|
|
8
9
|
import { ARTIFACT_ANALYSIS_OPERATIONS, artifactInventoryInputSchema, artifactExtractionExecutionSchema, } from "../contracts/artifactToolContracts.js";
|
|
@@ -15,7 +16,7 @@ import { ArtifactReaderFailure } from "./ArtifactReader.js";
|
|
|
15
16
|
import { ARTIFACT_PROVIDER_IDENTITY as IDENTITY, artifactCapabilities, } from "./ArtifactProviderMetadata.js";
|
|
16
17
|
import { createEvidence } from "../domain/evidence.js";
|
|
17
18
|
import { createArtifactInspection } from "../domain/artifactInspection.js";
|
|
18
|
-
import { resolveArtifactIntegrityPolicy } from "
|
|
19
|
+
import { resolveArtifactIntegrityPolicy } from "./inventory/policy.js";
|
|
19
20
|
/** Read-only inventory and exclusively owned extraction provider. */
|
|
20
21
|
export class ArtifactProvider {
|
|
21
22
|
#capabilities;
|
|
@@ -98,6 +99,8 @@ class ArtifactClient {
|
|
|
98
99
|
if (operation === "inspect_asset_catalog") {
|
|
99
100
|
return await this.inspectAssetCatalog(parameters, options);
|
|
100
101
|
}
|
|
102
|
+
if (operation === "trace_dylib_resolution")
|
|
103
|
+
return await this.traceDylibResolution(parameters, options);
|
|
101
104
|
if (operation === "extract_artifact") {
|
|
102
105
|
const parsed = artifactExtractionExecutionSchema.parse(parameters);
|
|
103
106
|
const result = await extractArtifact({
|
|
@@ -154,6 +157,30 @@ class ArtifactClient {
|
|
|
154
157
|
})),
|
|
155
158
|
}));
|
|
156
159
|
}
|
|
160
|
+
async traceDylibResolution(parameters, options) {
|
|
161
|
+
if (this.target.kind !== "executable" || this.target.format !== "mach-o")
|
|
162
|
+
throw new ArtifactReaderFailure("unavailable", "trace_dylib_resolution requires an active Mach-O or .app bundle target");
|
|
163
|
+
// Only a target opened from an app bundle directory carries its Info.plist;
|
|
164
|
+
// a regular file whose name ends in .app is a standalone image.
|
|
165
|
+
const bundle = this.target.bundleInfoPlist === undefined
|
|
166
|
+
? undefined
|
|
167
|
+
: this.target.sourcePath;
|
|
168
|
+
const result = await traceDylibResolution({
|
|
169
|
+
rootPath: bundle ?? dirname(this.target.path),
|
|
170
|
+
targetPath: this.target.path,
|
|
171
|
+
targetSha256: this.target.sha256,
|
|
172
|
+
enumerateRoots: bundle !== undefined,
|
|
173
|
+
parameters,
|
|
174
|
+
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
175
|
+
});
|
|
176
|
+
return ok(createAnalysisExecution(result, IDENTITY, {
|
|
177
|
+
limitations: result.limitations,
|
|
178
|
+
locations: result.images.map(({ path }) => ({
|
|
179
|
+
kind: "artifact-path",
|
|
180
|
+
path,
|
|
181
|
+
})),
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
157
184
|
async inspectArtifact(parameters, options) {
|
|
158
185
|
const parsed = artifactInventoryInputSchema.parse(parameters);
|
|
159
186
|
const inventoryParameters = {
|
|
@@ -11,7 +11,8 @@ export const artifactCapabilities = (platform = process.platform) => Object.free
|
|
|
11
11
|
effects: Object.freeze({
|
|
12
12
|
mutatesArtifact: false,
|
|
13
13
|
launchesProcess: operation !== "decode_interface_builder" &&
|
|
14
|
-
operation !== "inspect_keyed_archive"
|
|
14
|
+
operation !== "inspect_keyed_archive" &&
|
|
15
|
+
operation !== "trace_dylib_resolution",
|
|
15
16
|
mayShowUi: false,
|
|
16
17
|
mayAccessNetwork: false,
|
|
17
18
|
mayWriteFilesystem: operation === "extract_artifact" ||
|
|
@@ -39,6 +39,9 @@ export class ZipArtifactReader {
|
|
|
39
39
|
this.#reader = new ZipReader(this.#source, {
|
|
40
40
|
checkSignature: true,
|
|
41
41
|
checkOverlappingEntry: true,
|
|
42
|
+
// REA applies its own provider-neutral path validation in scanReader and
|
|
43
|
+
// must preserve that typed failure instead of zip.js rejecting first.
|
|
44
|
+
filenameValidation: "tolerant",
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
47
|
async *entries(signal) {
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, readlink, realpath } from "node:fs/promises";
|
|
4
|
+
import { join, relative, sep } from "node:path";
|
|
5
|
+
import { resolveTreePath } from "../../domain/apple/dyldPaths.js";
|
|
6
|
+
import { dylibResolutionInputSchema, dylibResolutionResultSchema, traceDylibLoading, } from "../../domain/apple/dylibResolution.js";
|
|
7
|
+
import { ArtifactReaderFailure } from "../ArtifactReader.js";
|
|
8
|
+
import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js";
|
|
9
|
+
import { hasMachoMagic, readMachoImage } from "./MachoLoadCommandReader.js";
|
|
10
|
+
const HASH_CHUNK_BYTES = 1024 * 1024;
|
|
11
|
+
/** Lazily probed, symlink-preserving view of one analyzed directory. */
|
|
12
|
+
class FilesystemTreeView {
|
|
13
|
+
root;
|
|
14
|
+
signal;
|
|
15
|
+
#entries = new Map();
|
|
16
|
+
#images = new Map();
|
|
17
|
+
#identities = new Map();
|
|
18
|
+
constructor(root, signal) {
|
|
19
|
+
this.root = root;
|
|
20
|
+
this.signal = signal;
|
|
21
|
+
}
|
|
22
|
+
entry(path) {
|
|
23
|
+
const cached = this.#entries.get(path);
|
|
24
|
+
if (cached !== undefined)
|
|
25
|
+
return cached;
|
|
26
|
+
const pending = this.#readEntry(path);
|
|
27
|
+
this.#entries.set(path, pending);
|
|
28
|
+
return pending;
|
|
29
|
+
}
|
|
30
|
+
image(path) {
|
|
31
|
+
const cached = this.#images.get(path);
|
|
32
|
+
if (cached !== undefined)
|
|
33
|
+
return cached;
|
|
34
|
+
const pending = readImage(join(this.root, path), this.signal).then(({ facts, identity }) => {
|
|
35
|
+
this.#identities.set(path, identity);
|
|
36
|
+
return facts;
|
|
37
|
+
});
|
|
38
|
+
this.#images.set(path, pending);
|
|
39
|
+
return pending;
|
|
40
|
+
}
|
|
41
|
+
/** Identity of the file whose header bytes `image()` parsed. */
|
|
42
|
+
identity(path) {
|
|
43
|
+
return this.#identities.get(path);
|
|
44
|
+
}
|
|
45
|
+
async #readEntry(path) {
|
|
46
|
+
cancelled(this.signal);
|
|
47
|
+
const absolute = join(this.root, path);
|
|
48
|
+
try {
|
|
49
|
+
const metadata = await lstat(absolute);
|
|
50
|
+
if (metadata.isSymbolicLink())
|
|
51
|
+
return { kind: "symlink", target: await readlink(absolute) };
|
|
52
|
+
if (metadata.isDirectory())
|
|
53
|
+
return { kind: "directory" };
|
|
54
|
+
return metadata.isFile() ? { kind: "file" } : undefined;
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
if (missing(cause))
|
|
58
|
+
return undefined;
|
|
59
|
+
throw cause;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Resolve dyld load paths for the active Mach-O or every executable in its bundle. */
|
|
64
|
+
export const traceDylibResolution = async (options) => {
|
|
65
|
+
const parsed = dylibResolutionInputSchema.safeParse(options.parameters);
|
|
66
|
+
if (!parsed.success)
|
|
67
|
+
throw new ArtifactReaderFailure("path", "roots must be normalized paths relative to the analyzed root");
|
|
68
|
+
// Canonicalization is inside the translated region: a revoked permission
|
|
69
|
+
// keeps its reason and path.
|
|
70
|
+
let root = options.rootPath;
|
|
71
|
+
try {
|
|
72
|
+
root = await realpath(options.rootPath);
|
|
73
|
+
const target = relative(root, await realpath(options.targetPath))
|
|
74
|
+
.split(sep)
|
|
75
|
+
.join("/");
|
|
76
|
+
const view = new FilesystemTreeView(root, options.signal);
|
|
77
|
+
const { roots: requested, unclassified } = parsed.data.roots !== undefined
|
|
78
|
+
? { roots: parsed.data.roots, unclassified: [] }
|
|
79
|
+
: options.enumerateRoots
|
|
80
|
+
? await executableRoots(root, view, options.signal)
|
|
81
|
+
: { roots: [target], unclassified: [] };
|
|
82
|
+
const roots = await requireMachoRoots(view, requested);
|
|
83
|
+
const trace = await traceDylibLoading(view, {
|
|
84
|
+
roots,
|
|
85
|
+
unclassified,
|
|
86
|
+
...(parsed.data.architecture === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { architecture: parsed.data.architecture }),
|
|
89
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
90
|
+
});
|
|
91
|
+
// Each digest must describe the same file whose headers were parsed.
|
|
92
|
+
const digests = new Map();
|
|
93
|
+
for (const { path } of trace.images)
|
|
94
|
+
digests.set(path, await fileSha256(join(root, path), {
|
|
95
|
+
path,
|
|
96
|
+
expected: view.identity(path),
|
|
97
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
98
|
+
}));
|
|
99
|
+
const targetDigest = digests.get(target) ??
|
|
100
|
+
(await fileSha256(join(root, target), {
|
|
101
|
+
path: target,
|
|
102
|
+
expected: undefined,
|
|
103
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
104
|
+
}));
|
|
105
|
+
if (targetDigest !== options.targetSha256)
|
|
106
|
+
throw new ArtifactReaderFailure("integrity", `Active target digest changed: expected ${options.targetSha256}, observed ${targetDigest}`);
|
|
107
|
+
return dylibResolutionResultSchema.parse({
|
|
108
|
+
...trace,
|
|
109
|
+
root_path: options.rootPath,
|
|
110
|
+
target_sha256: options.targetSha256,
|
|
111
|
+
images: trace.images.map((image) => ({
|
|
112
|
+
...image,
|
|
113
|
+
sha256: digests.get(image.path),
|
|
114
|
+
})),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
if (options.signal?.aborted === true)
|
|
119
|
+
throw new ArtifactReaderFailure("cancelled", "Dylib resolution was cancelled", { cause });
|
|
120
|
+
const denied = permissionDenied(cause);
|
|
121
|
+
if (denied !== undefined)
|
|
122
|
+
throw new ArtifactReaderFailure("unavailable", `Permission denied (${denied.code}) reading ${deniedPath(root, denied.path)}`, { cause });
|
|
123
|
+
throw cause;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Every Mach-O in the bundle with an executable slice is its own process root.
|
|
128
|
+
* Mach-O files that do not parse are returned separately: whether they are
|
|
129
|
+
* executables is unknown, so they make coverage partial instead of vanishing.
|
|
130
|
+
*/
|
|
131
|
+
const executableRoots = async (root, view, signal) => {
|
|
132
|
+
const roots = [];
|
|
133
|
+
const unclassified = [];
|
|
134
|
+
for await (const entry of new DirectoryArtifactReader(root).entries(signal)) {
|
|
135
|
+
if (entry.kind !== "file" ||
|
|
136
|
+
!(await startsWithMachoMagic(entry.adapterKey)))
|
|
137
|
+
continue;
|
|
138
|
+
const facts = await view.image(entry.path);
|
|
139
|
+
if (facts.status === "malformed" || facts.status === "unsupported")
|
|
140
|
+
unclassified.push(entry.path);
|
|
141
|
+
else if (facts.status === "parsed" &&
|
|
142
|
+
facts.slices.some(({ file_type: type }) => type === "execute"))
|
|
143
|
+
roots.push(entry.path);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
roots: roots.sort(compare),
|
|
147
|
+
unclassified: unclassified.sort(compare),
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
151
|
+
/**
|
|
152
|
+
* Resolve each root segment by segment, as dependency candidates are, so a
|
|
153
|
+
* symlink in an intermediate directory cannot lead outside the analyzed root.
|
|
154
|
+
*/
|
|
155
|
+
const requireMachoRoots = async (view, roots) => {
|
|
156
|
+
const resolved = [];
|
|
157
|
+
for (const path of roots) {
|
|
158
|
+
const lookup = await resolveTreePath(view, path);
|
|
159
|
+
if (lookup.kind === "escapes")
|
|
160
|
+
throw new ArtifactReaderFailure("path", `Root ${path} resolves outside the analyzed root`);
|
|
161
|
+
if (lookup.kind !== "file")
|
|
162
|
+
throw new ArtifactReaderFailure("path", `Root ${path} is not a regular file in the analyzed root`);
|
|
163
|
+
if ((await view.image(lookup.path)).status === "not-mach-o")
|
|
164
|
+
throw new ArtifactReaderFailure("format", `Root ${path} is not a Mach-O image`);
|
|
165
|
+
if (!resolved.includes(lookup.path))
|
|
166
|
+
resolved.push(lookup.path);
|
|
167
|
+
}
|
|
168
|
+
return resolved;
|
|
169
|
+
};
|
|
170
|
+
const startsWithMachoMagic = async (path) => {
|
|
171
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
172
|
+
try {
|
|
173
|
+
const buffer = new Uint8Array(4);
|
|
174
|
+
const { bytesRead } = await handle.read(buffer, 0, 4, 0);
|
|
175
|
+
return hasMachoMagic(buffer.subarray(0, bytesRead));
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await handle.close();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const identityOf = (metadata) => [
|
|
182
|
+
metadata.dev,
|
|
183
|
+
metadata.ino,
|
|
184
|
+
metadata.size,
|
|
185
|
+
metadata.mtimeNs,
|
|
186
|
+
metadata.ctimeNs,
|
|
187
|
+
].join(":");
|
|
188
|
+
const readImage = async (path, signal) => {
|
|
189
|
+
cancelled(signal);
|
|
190
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
191
|
+
try {
|
|
192
|
+
const metadata = await handle.stat({ bigint: true });
|
|
193
|
+
const size = Number(metadata.size);
|
|
194
|
+
const facts = await readMachoImage(async (offset, length) => {
|
|
195
|
+
cancelled(signal);
|
|
196
|
+
const buffer = new Uint8Array(Math.max(0, Math.min(length, size - offset)));
|
|
197
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, offset);
|
|
198
|
+
return buffer.subarray(0, bytesRead);
|
|
199
|
+
}, size);
|
|
200
|
+
return { facts, identity: identityOf(metadata) };
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await handle.close();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/** Hash a file, failing if it is not the file (or the version) that was parsed. */
|
|
207
|
+
const fileSha256 = async (absolute, options) => {
|
|
208
|
+
const { signal } = options;
|
|
209
|
+
const handle = await open(absolute, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
210
|
+
const unchanged = async () => {
|
|
211
|
+
if (options.expected !== undefined &&
|
|
212
|
+
identityOf(await handle.stat({ bigint: true })) !== options.expected)
|
|
213
|
+
throw new ArtifactReaderFailure("integrity", `${options.path} changed while its dyld load commands were traced`);
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
await unchanged();
|
|
217
|
+
const hash = createHash("sha256");
|
|
218
|
+
const buffer = new Uint8Array(HASH_CHUNK_BYTES);
|
|
219
|
+
for (;;) {
|
|
220
|
+
cancelled(signal);
|
|
221
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
222
|
+
if (bytesRead === 0)
|
|
223
|
+
break;
|
|
224
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
225
|
+
}
|
|
226
|
+
await unchanged();
|
|
227
|
+
return hash.digest("hex");
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
await handle.close();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const cancelled = (signal) => {
|
|
234
|
+
if (signal?.aborted === true)
|
|
235
|
+
throw new ArtifactReaderFailure("cancelled", "Dylib resolution was cancelled");
|
|
236
|
+
};
|
|
237
|
+
/** A denied path relative to the analyzed root, or absolute when outside it. */
|
|
238
|
+
const deniedPath = (root, path) => {
|
|
239
|
+
if (path === undefined)
|
|
240
|
+
return "a file in the analyzed root";
|
|
241
|
+
const inside = relative(root, path);
|
|
242
|
+
return inside === "" || inside.startsWith("..") ? path : inside;
|
|
243
|
+
};
|
|
244
|
+
/** Host permission denials, kept distinct from malformed or missing files. */
|
|
245
|
+
const permissionDenied = (cause) => {
|
|
246
|
+
if (!(cause instanceof Error) || !("code" in cause))
|
|
247
|
+
return undefined;
|
|
248
|
+
if (cause.code !== "EACCES" && cause.code !== "EPERM")
|
|
249
|
+
return undefined;
|
|
250
|
+
return {
|
|
251
|
+
code: cause.code,
|
|
252
|
+
path: "path" in cause && typeof cause.path === "string"
|
|
253
|
+
? cause.path
|
|
254
|
+
: undefined,
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
const missing = (cause) => cause instanceof Error &&
|
|
258
|
+
"code" in cause &&
|
|
259
|
+
(cause.code === "ENOENT" || cause.code === "ENOTDIR");
|