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
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import Darwin
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct RunTokenObservation: Encodable {
|
|
5
|
+
let pid: Int32
|
|
6
|
+
let state: String
|
|
7
|
+
let run_id: String?
|
|
8
|
+
let reason: String?
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct ProcessIdentityObservation: Encodable {
|
|
12
|
+
let pid: Int32
|
|
13
|
+
let state: String
|
|
14
|
+
let identity: String?
|
|
15
|
+
let reason: String?
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private struct ProcessMetadata {
|
|
19
|
+
let identity: String
|
|
20
|
+
let pointerSize: Int
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct ProcessSnapshot {
|
|
24
|
+
fileprivate let metadata: [Int32: ProcessMetadata]
|
|
25
|
+
let failure: String?
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
typealias ProcessTableSysctlCall = (
|
|
29
|
+
_ mib: inout [Int32],
|
|
30
|
+
_ output: UnsafeMutableRawPointer?,
|
|
31
|
+
_ size: inout Int
|
|
32
|
+
) -> Int32
|
|
33
|
+
|
|
34
|
+
private func readProcessSnapshot() -> ProcessSnapshot {
|
|
35
|
+
readProcessSnapshot { mib, output, size in
|
|
36
|
+
sysctl(&mib, u_int(mib.count), output, &size, nil, 0)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func readProcessSnapshot(
|
|
41
|
+
sysctlCall: ProcessTableSysctlCall
|
|
42
|
+
) -> ProcessSnapshot {
|
|
43
|
+
for _ in 0..<3 {
|
|
44
|
+
var mib = [CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0]
|
|
45
|
+
var size = 0
|
|
46
|
+
guard sysctlCall(&mib, nil, &size) == 0 else {
|
|
47
|
+
let failure = errno
|
|
48
|
+
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(failure)")
|
|
49
|
+
}
|
|
50
|
+
guard size >= MemoryLayout<kinfo_proc>.size,
|
|
51
|
+
size % MemoryLayout<kinfo_proc>.stride == 0 else {
|
|
52
|
+
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(errno)")
|
|
53
|
+
}
|
|
54
|
+
var processes = [kinfo_proc](
|
|
55
|
+
repeating: kinfo_proc(),
|
|
56
|
+
count: size / MemoryLayout<kinfo_proc>.stride
|
|
57
|
+
)
|
|
58
|
+
let result = processes.withUnsafeMutableBufferPointer { buffer in
|
|
59
|
+
let output = buffer.baseAddress.map { UnsafeMutableRawPointer($0) }
|
|
60
|
+
return sysctlCall(&mib, output, &size)
|
|
61
|
+
}
|
|
62
|
+
guard result == 0 else {
|
|
63
|
+
let failure = errno
|
|
64
|
+
if failure == ENOMEM { continue }
|
|
65
|
+
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(failure)")
|
|
66
|
+
}
|
|
67
|
+
guard size % MemoryLayout<kinfo_proc>.stride == 0 else {
|
|
68
|
+
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(errno)")
|
|
69
|
+
}
|
|
70
|
+
let processCount = size / MemoryLayout<kinfo_proc>.stride
|
|
71
|
+
let metadata = Dictionary(
|
|
72
|
+
processes.prefix(processCount).map { process in
|
|
73
|
+
let start = process.kp_proc.p_starttime
|
|
74
|
+
return (
|
|
75
|
+
process.kp_proc.p_pid,
|
|
76
|
+
ProcessMetadata(
|
|
77
|
+
identity: "\(start.tv_sec):\(start.tv_usec)",
|
|
78
|
+
pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
},
|
|
82
|
+
uniquingKeysWith: { first, _ in first }
|
|
83
|
+
)
|
|
84
|
+
return ProcessSnapshot(metadata: metadata, failure: nil)
|
|
85
|
+
}
|
|
86
|
+
return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] {
|
|
90
|
+
let snapshot = readProcessSnapshot()
|
|
91
|
+
return pids.map { pid in
|
|
92
|
+
guard let metadata = snapshot.metadata[pid] else {
|
|
93
|
+
return unavailableIdentity(
|
|
94
|
+
pid: pid,
|
|
95
|
+
reason: snapshot.failure ?? "process_not_in_snapshot"
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
return ProcessIdentityObservation(pid: pid, state: "readable", identity: metadata.identity, reason: nil)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityObservation {
|
|
103
|
+
ProcessIdentityObservation(pid: pid, state: "unavailable", identity: nil, reason: reason)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enum RunTokenReadError: Error {
|
|
107
|
+
case invalidBuffer
|
|
108
|
+
case duplicateToken
|
|
109
|
+
case invalidTokenEncoding
|
|
110
|
+
case environmentUnavailable
|
|
111
|
+
case appleVectorUnavailable
|
|
112
|
+
case ambiguousEnvironmentBoundary
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation {
|
|
116
|
+
var mib = [CTL_KERN, KERN_PROCARGS2, Int32(pid)]
|
|
117
|
+
var size = 0
|
|
118
|
+
guard sysctl(&mib, u_int(mib.count), nil, &size, nil, 0) == 0, size >= MemoryLayout<Int32>.size else {
|
|
119
|
+
return unavailable(pid: pid, reason: "sysctl_failed_\(errno)")
|
|
120
|
+
}
|
|
121
|
+
var bytes = [UInt8](repeating: 0, count: size)
|
|
122
|
+
let result = bytes.withUnsafeMutableBytes { buffer in
|
|
123
|
+
sysctl(&mib, u_int(mib.count), buffer.baseAddress, &size, nil, 0)
|
|
124
|
+
}
|
|
125
|
+
guard result == 0 else {
|
|
126
|
+
return unavailable(pid: pid, reason: "sysctl_failed_\(errno)")
|
|
127
|
+
}
|
|
128
|
+
bytes = Array(bytes.prefix(size))
|
|
129
|
+
do {
|
|
130
|
+
return RunTokenObservation(pid: pid, state: "readable", run_id: try token(in: bytes, pointerSize: pointerSize), reason: nil)
|
|
131
|
+
} catch RunTokenReadError.duplicateToken {
|
|
132
|
+
return unavailable(pid: pid, reason: "duplicate_run_token")
|
|
133
|
+
} catch RunTokenReadError.invalidTokenEncoding {
|
|
134
|
+
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
|
|
135
|
+
} catch RunTokenReadError.environmentUnavailable {
|
|
136
|
+
return unavailable(pid: pid, reason: "environment_unavailable")
|
|
137
|
+
} catch RunTokenReadError.appleVectorUnavailable {
|
|
138
|
+
return unavailable(pid: pid, reason: "apple_vector_unavailable")
|
|
139
|
+
} catch RunTokenReadError.ambiguousEnvironmentBoundary {
|
|
140
|
+
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
|
|
141
|
+
} catch {
|
|
142
|
+
return unavailable(pid: pid, reason: "malformed_procargs")
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
func readRunTokens(pids: [Int32]) -> [RunTokenObservation] {
|
|
147
|
+
let before = readProcessSnapshot()
|
|
148
|
+
let observations = pids.map { pid -> RunTokenObservation in
|
|
149
|
+
guard let metadata = before.metadata[pid] else {
|
|
150
|
+
return unavailable(pid: pid, reason: before.failure ?? "process_not_in_snapshot")
|
|
151
|
+
}
|
|
152
|
+
return readRunToken(pid: pid, pointerSize: metadata.pointerSize)
|
|
153
|
+
}
|
|
154
|
+
let after = readProcessSnapshot()
|
|
155
|
+
return zip(pids, observations).map { pid, observation in
|
|
156
|
+
guard let initial = before.metadata[pid], let current = after.metadata[pid] else {
|
|
157
|
+
return unavailable(pid: pid, reason: after.failure ?? "process_not_in_snapshot")
|
|
158
|
+
}
|
|
159
|
+
guard initial.identity == current.identity,
|
|
160
|
+
initial.pointerSize == current.pointerSize else {
|
|
161
|
+
return unavailable(pid: pid, reason: "process_identity_changed_during_token_read")
|
|
162
|
+
}
|
|
163
|
+
return observation
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout<UnsafeRawPointer>.size) throws -> String? {
|
|
168
|
+
guard bytes.count >= MemoryLayout<Int32>.size else { throw RunTokenReadError.invalidBuffer }
|
|
169
|
+
let argc = bytes.withUnsafeBytes { $0.loadUnaligned(as: Int32.self).littleEndian }
|
|
170
|
+
guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer }
|
|
171
|
+
var offset = MemoryLayout<Int32>.size
|
|
172
|
+
guard let executablePath = readString(in: bytes, offset: &offset) else {
|
|
173
|
+
throw RunTokenReadError.invalidBuffer
|
|
174
|
+
}
|
|
175
|
+
guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBuffer }
|
|
176
|
+
let pathRecordSize = Array("executable_path=".utf8).count + executablePath.count + 1
|
|
177
|
+
let padding = (pointerSize - pathRecordSize % pointerSize) % pointerSize
|
|
178
|
+
guard offset + padding <= bytes.count,
|
|
179
|
+
bytes[offset..<(offset + padding)].allSatisfy({ $0 == 0 }) else {
|
|
180
|
+
throw RunTokenReadError.invalidBuffer
|
|
181
|
+
}
|
|
182
|
+
offset += padding
|
|
183
|
+
for _ in 0..<argc {
|
|
184
|
+
guard readString(in: bytes, offset: &offset, allowEmpty: true) != nil else {
|
|
185
|
+
throw RunTokenReadError.invalidBuffer
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
guard bytes.last == 0 else { throw RunTokenReadError.invalidBuffer }
|
|
189
|
+
if offset == bytes.count {
|
|
190
|
+
throw RunTokenReadError.environmentUnavailable
|
|
191
|
+
}
|
|
192
|
+
var trailingEntries: [ArraySlice<UInt8>] = []
|
|
193
|
+
var zeroedAppleVectorStart: Int?
|
|
194
|
+
while offset < bytes.count {
|
|
195
|
+
if bytes[offset] == 0 {
|
|
196
|
+
let start = offset
|
|
197
|
+
while offset < bytes.count, bytes[offset] == 0 {
|
|
198
|
+
offset += 1
|
|
199
|
+
}
|
|
200
|
+
if offset - start > 1 {
|
|
201
|
+
zeroedAppleVectorStart = zeroedAppleVectorStart ?? trailingEntries.count
|
|
202
|
+
}
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
guard let entry = readString(in: bytes, offset: &offset) else {
|
|
206
|
+
throw RunTokenReadError.invalidBuffer
|
|
207
|
+
}
|
|
208
|
+
trailingEntries.append(entry)
|
|
209
|
+
}
|
|
210
|
+
// XNU appends its Apple vector after the environment. libc clears the
|
|
211
|
+
// initial `pfz=` record after startup, leaving a multi-NUL run in its place.
|
|
212
|
+
// A caller can also set `pfz`, so prefer the final intact sentinel when it
|
|
213
|
+
// remains available and otherwise stop at the cleared record.
|
|
214
|
+
let intactAppleVectorStart = trailingEntries.lastIndex(where: {
|
|
215
|
+
$0.starts(with: Array("pfz=".utf8))
|
|
216
|
+
})
|
|
217
|
+
let preferZeroedBoundary = zeroedAppleVectorStart.flatMap { zeroedStart in
|
|
218
|
+
intactAppleVectorStart.map { intactStart in intactStart < zeroedStart } ?? true
|
|
219
|
+
} ?? false
|
|
220
|
+
let appleVectorStart = preferZeroedBoundary
|
|
221
|
+
? zeroedAppleVectorStart
|
|
222
|
+
: intactAppleVectorStart
|
|
223
|
+
guard let appleVectorStart else {
|
|
224
|
+
throw RunTokenReadError.appleVectorUnavailable
|
|
225
|
+
}
|
|
226
|
+
if trailingEntries[appleVectorStart...].contains(where: {
|
|
227
|
+
$0.starts(with: Array("REA_PROCESS_RUN_ID=".utf8))
|
|
228
|
+
}) {
|
|
229
|
+
throw RunTokenReadError.ambiguousEnvironmentBoundary
|
|
230
|
+
}
|
|
231
|
+
let environmentEntries = trailingEntries[..<appleVectorStart]
|
|
232
|
+
guard !environmentEntries.isEmpty else {
|
|
233
|
+
throw RunTokenReadError.environmentUnavailable
|
|
234
|
+
}
|
|
235
|
+
var found: String?
|
|
236
|
+
for entry in environmentEntries {
|
|
237
|
+
if entry.starts(with: Array("REA_PROCESS_RUN_ID=".utf8)) {
|
|
238
|
+
guard found == nil else { throw RunTokenReadError.duplicateToken }
|
|
239
|
+
let value = entry.dropFirst("REA_PROCESS_RUN_ID=".utf8.count)
|
|
240
|
+
guard let decoded = String(bytes: value, encoding: .utf8) else {
|
|
241
|
+
throw RunTokenReadError.invalidTokenEncoding
|
|
242
|
+
}
|
|
243
|
+
found = decoded
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return found
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private func readString(
|
|
250
|
+
in bytes: [UInt8],
|
|
251
|
+
offset: inout Int,
|
|
252
|
+
allowEmpty: Bool = false
|
|
253
|
+
) -> ArraySlice<UInt8>? {
|
|
254
|
+
guard offset < bytes.count, let end = bytes[offset...].firstIndex(of: 0),
|
|
255
|
+
allowEmpty || end > offset else { return nil }
|
|
256
|
+
let string = bytes[offset..<end]
|
|
257
|
+
offset = end + 1
|
|
258
|
+
return string
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private func unavailable(pid: Int32, reason: String) -> RunTokenObservation {
|
|
262
|
+
RunTokenObservation(pid: pid, state: "unavailable", run_id: nil, reason: reason)
|
|
263
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct RunTokenResponse: Encodable {
|
|
4
|
+
let results: [RunTokenObservation]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
struct ProcessIdentityResponse: Encodable {
|
|
8
|
+
let results: [ProcessIdentityObservation]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@main
|
|
12
|
+
struct ProcessRunTokenReaderMain {
|
|
13
|
+
static func main() {
|
|
14
|
+
let arguments = Array(CommandLine.arguments.dropFirst())
|
|
15
|
+
let identityMode = arguments.first == "--identities"
|
|
16
|
+
let pidArguments = identityMode ? Array(arguments.dropFirst()) : arguments
|
|
17
|
+
let pids = pidArguments.compactMap(Int32.init)
|
|
18
|
+
guard pids.count == pidArguments.count else {
|
|
19
|
+
fputs("invalid process ID input\n", stderr)
|
|
20
|
+
exit(64)
|
|
21
|
+
}
|
|
22
|
+
do {
|
|
23
|
+
let output: Data
|
|
24
|
+
if identityMode {
|
|
25
|
+
output = try JSONEncoder().encode(ProcessIdentityResponse(results: readProcessIdentities(pids: pids)))
|
|
26
|
+
} else {
|
|
27
|
+
output = try JSONEncoder().encode(RunTokenResponse(results: readRunTokens(pids: pids)))
|
|
28
|
+
}
|
|
29
|
+
FileHandle.standardOutput.write(output)
|
|
30
|
+
} catch {
|
|
31
|
+
fputs("could not encode process token observations\n", stderr)
|
|
32
|
+
exit(70)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Fixed core-only GDB/pwndbg operation; no caller commands or historical PID lookup."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import resource
|
|
6
|
+
import gdb
|
|
7
|
+
|
|
8
|
+
OUTPUT_BYTES = 64 * 1024 * 1024
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ContextFailure(Exception):
|
|
12
|
+
def __init__(self, reason, message):
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
self.reason = reason
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def inspect(request):
|
|
18
|
+
if gdb.selected_inferior().connection is not None:
|
|
19
|
+
raise ContextFailure("decoder", "Owned GDB unexpectedly has an inferior connection before core loading.")
|
|
20
|
+
root = Path(os.environ["REA_CORE_CONTEXT_REQUEST"]).parent
|
|
21
|
+
empty = root / "no-libraries"
|
|
22
|
+
empty.mkdir(mode=0o700)
|
|
23
|
+
for command in ["set auto-load off", "set debuginfod enabled off", "set auto-solib-add off", "set history save off", "set pagination off",
|
|
24
|
+
"set sysroot ./no-libraries", "set solib-search-path ./no-libraries"]:
|
|
25
|
+
gdb.execute(command, to_string=True)
|
|
26
|
+
entry = request["gdbinit"]
|
|
27
|
+
try:
|
|
28
|
+
exec(compile(Path(entry).read_bytes(), entry, "exec"), {"__name__": "__main__", "__file__": entry})
|
|
29
|
+
import pwndbg.aglib.vmmap
|
|
30
|
+
import pwndbg.lib.version
|
|
31
|
+
except (ImportError, OSError) as error:
|
|
32
|
+
raise ContextFailure("unavailable", "Selected pwndbg could not initialize: " + str(error)) from error
|
|
33
|
+
version = pwndbg.lib.version.__version__
|
|
34
|
+
if version != "2026.09.15" and not version.startswith("2026.09.15 build: "):
|
|
35
|
+
raise ContextFailure("unsupported", "Expected unchanged pwndbg2026.09.15; selected plugin reports " + version + ".")
|
|
36
|
+
gdb.execute('set context-sections ""', to_string=True)
|
|
37
|
+
# core-file takes a raw filename and retains quote characters. Use a fixed
|
|
38
|
+
# relative filename in the owned cwd so no selected path becomes GDB syntax.
|
|
39
|
+
if Path(request["snapshot_path"]) != root.parent / "object.snapshot":
|
|
40
|
+
raise ContextFailure("decoder", "Core context request did not select the shared owned snapshot.")
|
|
41
|
+
gdb.execute("core-file ../object.snapshot", to_string=True)
|
|
42
|
+
inferior = gdb.selected_inferior()
|
|
43
|
+
if inferior.connection is None or inferior.connection.type != "core" or gdb.current_progspace().filename is not None:
|
|
44
|
+
raise ContextFailure("decoder", "Requested core-only boundary was not established; no mapping query performed.")
|
|
45
|
+
maps = []
|
|
46
|
+
for page in pwndbg.aglib.vmmap.get():
|
|
47
|
+
flags = page.flags
|
|
48
|
+
maps.append({"start": hex(page.start), "end": hex(page.end), "offset": hex(page.offset), "reported_flags": flags,
|
|
49
|
+
"permissions": {"read": bool(flags & 4), "write": bool(flags & 2), "execute": bool(flags & 1)} if 1 <= flags <= 7 else None,
|
|
50
|
+
"pathname_display": page.objfile, "current_file_identity": "unknown"})
|
|
51
|
+
return {"status": "available", "gdb_version": gdb.VERSION, "pwndbg_version": version, "connection": "core", "executable": None,
|
|
52
|
+
"historical_pid": inferior.pid, "reported_limits": {"address_space_bytes": resource.getrlimit(resource.RLIMIT_AS)[0], "cpu_seconds": resource.getrlimit(resource.RLIMIT_CPU)[0], "file_size_bytes": resource.getrlimit(resource.RLIMIT_FSIZE)[0]}, "maps": maps, "confidence": "derived", "completeness": "unknown",
|
|
53
|
+
"diagnostics": {"stdout": "", "stderr": "", "truncated": False}}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def failure_exit(request_path, status, marker):
|
|
57
|
+
try:
|
|
58
|
+
descriptor = os.open(Path(request_path).parent / "resource.failure", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
59
|
+
os.write(descriptor, marker)
|
|
60
|
+
os.close(descriptor)
|
|
61
|
+
except (OSError, MemoryError):
|
|
62
|
+
pass
|
|
63
|
+
os._exit(status)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
request_path = os.environ["REA_CORE_CONTEXT_REQUEST"]
|
|
68
|
+
request = json.loads(Path(request_path).read_text(encoding="utf-8"))
|
|
69
|
+
reserve = bytearray(1024 * 1024)
|
|
70
|
+
try:
|
|
71
|
+
reply = {"ok": True, "value": inspect(request)}
|
|
72
|
+
except ContextFailure as error:
|
|
73
|
+
reply = {"ok": False, "reason": error.reason, "message": str(error)}
|
|
74
|
+
except MemoryError:
|
|
75
|
+
reserve = None
|
|
76
|
+
failure_exit(request_path, 75, b"M")
|
|
77
|
+
except Exception as error:
|
|
78
|
+
if isinstance(error, OSError) and error.errno == 12:
|
|
79
|
+
reserve = None
|
|
80
|
+
failure_exit(request_path, 75, b"M")
|
|
81
|
+
reply = {"ok": False, "reason": "decoder", "message": type(error).__name__ + ": " + str(error)}
|
|
82
|
+
try:
|
|
83
|
+
encoded = json.dumps(reply, ensure_ascii=True, allow_nan=False).encode("utf-8")
|
|
84
|
+
if len(encoded) > OUTPUT_BYTES:
|
|
85
|
+
encoded = json.dumps({"ok": False, "reason": "output-limit", "message": "Complete debugger context exceeds64 MiB reply budget."}).encode("utf-8")
|
|
86
|
+
descriptor = os.open(request["reply_path"], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
87
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
88
|
+
handle.write(encoded)
|
|
89
|
+
except MemoryError:
|
|
90
|
+
reserve = None
|
|
91
|
+
failure_exit(request_path, 75, b"M")
|
|
92
|
+
except OSError as error:
|
|
93
|
+
if error.errno == 27:
|
|
94
|
+
failure_exit(request_path, 76, b"F")
|
|
95
|
+
if error.errno == 12:
|
|
96
|
+
reserve = None
|
|
97
|
+
failure_exit(request_path, 75, b"M")
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Catch typed resource failures while loading or running the owned debugger launcher.
|
|
2
|
+
|
|
3
|
+
Only this package's fixed sibling is executed; caller input stays in request.json.
|
|
4
|
+
Interpreter startup failures before this boundary remain unclassified.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resource_exit(status, marker):
|
|
11
|
+
try:
|
|
12
|
+
if len(sys.argv) > 2:
|
|
13
|
+
descriptor = os.open(sys.argv[1], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
14
|
+
os.write(descriptor, marker)
|
|
15
|
+
os.close(descriptor)
|
|
16
|
+
except (OSError, MemoryError):
|
|
17
|
+
# Without a complete marker the parent keeps this failure unclassified.
|
|
18
|
+
pass
|
|
19
|
+
os._exit(status)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
implementation = os.path.join(os.path.dirname(__file__), "launch_impl.py")
|
|
23
|
+
with open(implementation, "rb") as handle:
|
|
24
|
+
source = handle.read()
|
|
25
|
+
exec(compile(source, implementation, "exec"), {
|
|
26
|
+
"__file__": implementation,
|
|
27
|
+
"__name__": "__main__",
|
|
28
|
+
})
|
|
29
|
+
except MemoryError:
|
|
30
|
+
# Reserved status: an observed allocation failure, including imports/compile.
|
|
31
|
+
resource_exit(75, b"M")
|
|
32
|
+
except OSError as error:
|
|
33
|
+
# Linux ENOMEM is an observed allocation failure, independent of error text.
|
|
34
|
+
if error.errno == 12:
|
|
35
|
+
resource_exit(75, b"M")
|
|
36
|
+
# Linux EFBIG is observed directly, including a partial limits/reply write.
|
|
37
|
+
if error.errno == 27:
|
|
38
|
+
resource_exit(76, b"F")
|
|
39
|
+
raise
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Lower inherited limits before replacing this owned process with GDB."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
runtime_path = Path(__file__).parent.parent / "pwntools" / "decoder_runtime.py"
|
|
8
|
+
runtime = {"__file__": str(runtime_path), "__name__": "rea_decoder_runtime"}
|
|
9
|
+
exec(compile(runtime_path.read_bytes(), str(runtime_path), "exec"), runtime)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LaunchFailure(Exception):
|
|
13
|
+
def __init__(self, reason, message):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.reason = reason
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def launch(snapshot, cache):
|
|
19
|
+
request_path = sys.argv[-1]
|
|
20
|
+
request = json.loads(Path(request_path).read_text(encoding="utf-8"))
|
|
21
|
+
bridge = str(Path(__file__).with_name("core_context.py"))
|
|
22
|
+
environment = dict(os.environ, REA_CORE_CONTEXT_REQUEST=request_path)
|
|
23
|
+
# Only fixed package code appears in the GDB Python command. Selected paths stay JSON data.
|
|
24
|
+
command = "python exec(compile(open(" + repr(bridge) + ", 'rb').read(), " + repr(bridge) + ", 'exec'), {'__name__':'__main__','__file__':" + repr(bridge) + "})"
|
|
25
|
+
try:
|
|
26
|
+
os.execve(request["gdb"], [request["gdb"], "-nx", "-nh", "--batch", "--quiet",
|
|
27
|
+
"-iex", "set auto-load off", "-iex", "set debuginfod enabled off",
|
|
28
|
+
"-iex", "set auto-solib-add off", "-ex", command], environment)
|
|
29
|
+
except OSError as error:
|
|
30
|
+
raise LaunchFailure("unavailable", "Selected GDB could not start (" + str(error.errno) + "): " + request["gdb"] + "; " + str(error)) from error
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
runtime["main"](sys.argv[-1], launch, LaunchFailure, "pwndbg-core-launcher")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Shared bounded reply/limit protocol, independent of the supplied artifact format."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import resource
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
OUTPUT_BYTES = 64 * 1024 * 1024
|
|
9
|
+
|
|
10
|
+
def lower_resource_limits():
|
|
11
|
+
limits = {}
|
|
12
|
+
for name, kind, maximum in (
|
|
13
|
+
("address_space_bytes", resource.RLIMIT_AS, 3 * 1024**3),
|
|
14
|
+
("cpu_seconds", resource.RLIMIT_CPU, 30),
|
|
15
|
+
("file_size_bytes", resource.RLIMIT_FSIZE, OUTPUT_BYTES),
|
|
16
|
+
):
|
|
17
|
+
soft, hard = resource.getrlimit(kind)
|
|
18
|
+
finite = [maximum] + [value for value in (soft, hard) if value != resource.RLIM_INFINITY]
|
|
19
|
+
effective = min(finite)
|
|
20
|
+
# Preserve the inherited hard boundary and never raise a caller's soft limit.
|
|
21
|
+
resource.setrlimit(kind, (effective, hard))
|
|
22
|
+
limits[name] = effective
|
|
23
|
+
return limits
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(request_path, inspect, failure_type, profile):
|
|
27
|
+
request = json.loads(Path(request_path).read_text(encoding="utf-8"))
|
|
28
|
+
os.environ["PWNLIB_NOTERM"] = "1"
|
|
29
|
+
os.environ["PWNLIB_CACHE_DIR"] = str(Path(request_path).parent / "cache")
|
|
30
|
+
limits = None
|
|
31
|
+
# Release bounded emergency headroom before constructing a MemoryError reply.
|
|
32
|
+
# The outer exit-status contract also covers allocation failure during serialization.
|
|
33
|
+
memory_reserve = bytearray(1024 * 1024)
|
|
34
|
+
try:
|
|
35
|
+
limits = lower_resource_limits()
|
|
36
|
+
descriptor = os.open(Path(request_path).parent / "limits.json", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
37
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
38
|
+
json.dump(limits, handle)
|
|
39
|
+
value = inspect(Path(request["snapshot_path"]), Path(request_path).parent / "cache")
|
|
40
|
+
value["limitations"].append("Effective owned Python resource soft limits: " + json.dumps(limits, sort_keys=True) + ". Inherited tighter limits are retained.")
|
|
41
|
+
reply = {"ok": True, "profile": profile, "value": value}
|
|
42
|
+
except failure_type as error:
|
|
43
|
+
reply = {"ok": False, "reason": error.reason, "message": str(error)}
|
|
44
|
+
except MemoryError:
|
|
45
|
+
memory_reserve = None
|
|
46
|
+
reply = {"ok": False, "reason": "resource-limit", "message": "pwntools memory allocation failed under the effective resource limits; the exact allocation cause is unknown.", "reported_limits": limits}
|
|
47
|
+
except Exception as error:
|
|
48
|
+
if isinstance(error, OSError) and error.errno == 12:
|
|
49
|
+
memory_reserve = None
|
|
50
|
+
reply = {"ok": False, "reason": "resource-limit", "message": "pwntools reported OSError(ENOMEM); the exact allocation cause is unknown.", "reported_limits": limits}
|
|
51
|
+
else:
|
|
52
|
+
reply = {"ok": False, "reason": "decoder", "message": type(error).__name__ + ": " + str(error)}
|
|
53
|
+
encoded = json.dumps(reply, ensure_ascii=True, allow_nan=False).encode("utf-8")
|
|
54
|
+
if len(encoded) > OUTPUT_BYTES:
|
|
55
|
+
encoded = json.dumps({"ok": False, "reason": "output-limit", "message": "Complete artifact result exceeds the 64 MiB reply budget."}).encode("utf-8")
|
|
56
|
+
descriptor = os.open(request["reply_path"], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
57
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
58
|
+
handle.write(encoded)
|
|
59
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Catch typed resource failures while loading or running the owned ELF adapter.
|
|
2
|
+
|
|
3
|
+
Only this package's fixed sibling is executed; caller input stays in request.json.
|
|
4
|
+
Interpreter startup failures before this boundary remain unclassified.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resource_exit(status, marker):
|
|
11
|
+
try:
|
|
12
|
+
if len(sys.argv) > 2:
|
|
13
|
+
descriptor = os.open(sys.argv[1], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
14
|
+
os.write(descriptor, marker)
|
|
15
|
+
os.close(descriptor)
|
|
16
|
+
except (OSError, MemoryError):
|
|
17
|
+
# Without a complete marker the parent keeps this failure unclassified.
|
|
18
|
+
pass
|
|
19
|
+
os._exit(status)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
implementation = os.path.join(os.path.dirname(__file__), "layout_impl.py")
|
|
23
|
+
with open(implementation, "rb") as handle:
|
|
24
|
+
source = handle.read()
|
|
25
|
+
exec(compile(source, implementation, "exec"), {
|
|
26
|
+
"__file__": implementation,
|
|
27
|
+
"__name__": "__main__",
|
|
28
|
+
})
|
|
29
|
+
except MemoryError:
|
|
30
|
+
# Reserved status: an observed allocation failure, including imports/compile.
|
|
31
|
+
resource_exit(75, b"M")
|
|
32
|
+
except OSError as error:
|
|
33
|
+
# Linux ENOMEM is an observed allocation failure, independent of error text.
|
|
34
|
+
if error.errno == 12:
|
|
35
|
+
resource_exit(75, b"M")
|
|
36
|
+
# Linux EFBIG is observed directly, including a partial limits/reply write.
|
|
37
|
+
if error.errno == 27:
|
|
38
|
+
resource_exit(76, b"F")
|
|
39
|
+
raise
|