@@ -19,6 +19,8 @@ import type { Context } from "vm";
1919import {
2020 getJazzerJsGlobal ,
2121 guideTowardsContainment ,
22+ registerAfterEachCallback ,
23+ reportFinding ,
2224 reportAndThrowFinding ,
2325} from "@jazzer.js/core" ;
2426import { registerBeforeHook } from "@jazzer.js/hooking" ;
@@ -32,11 +34,19 @@ import {
3234 IgnoreList ,
3335 type IgnoreRule ,
3436} from "../shared/finding-suppression" ;
35-
36- const BASE_CANARY_NAME = "jaz_zer" ;
37+ import {
38+ ensureCanaryInstalled ,
39+ ensureCanaryInstalledOnAnyTarget ,
40+ } from "../shared/code-injection-canary" ;
3741
3842export type { IgnoreRule } from "../shared/finding-suppression" ;
3943
44+ type PendingAccess = {
45+ canaryName : string ;
46+ stack : string ;
47+ handledByInvocation : boolean ;
48+ } ;
49+
4050/**
4151 * Configuration for the Code Injection bug detector.
4252 * Controls the reporting and suppression of dynamic code evaluation findings.
@@ -105,8 +115,10 @@ const config = new CodeInjectionConfigImpl();
105115bugDetectorConfigurations . set ( "code-injection" , config ) ;
106116
107117const installedCanaries = new WeakMap < object , string > ( ) ;
118+ const pendingAccesses : PendingAccess [ ] = [ ] ;
108119
109120ensureKnownCanariesInstalled ( ) ;
121+ registerAfterEachCallback ( flushPendingAccesses ) ;
110122
111123registerBeforeHook (
112124 "eval" ,
@@ -153,11 +165,14 @@ registerBeforeHook(
153165) ;
154166
155167function ensureKnownCanariesInstalled ( ) : void {
156- ensureCanaryInstalled ( globalThis ) ;
157- const vmContext = getVmContext ( ) ;
158- if ( vmContext ) {
159- ensureCanaryInstalled ( vmContext ) ;
160- }
168+ ensureCanaryInstalledOnAnyTarget (
169+ [
170+ { label : "globalThis" , object : globalThis } ,
171+ { label : "vmContext" , object : getVmContext ( ) } ,
172+ ] ,
173+ installedCanaries ,
174+ createCanaryDescriptor ,
175+ ) ;
161176}
162177
163178function getVmContext ( ) : Context | undefined {
@@ -167,48 +182,39 @@ function getVmContext(): Context | undefined {
167182function getActiveCanaryName ( ) : string {
168183 const vmContext = getVmContext ( ) ;
169184 return vmContext
170- ? ensureCanaryInstalled ( vmContext )
171- : ensureCanaryInstalled ( globalThis ) ;
172- }
173-
174- function ensureCanaryInstalled ( target : object ) : string {
175- const knownCanaryName = installedCanaries . get ( target ) ;
176- if ( knownCanaryName ) {
177- return knownCanaryName ;
178- }
179-
180- let canaryName = BASE_CANARY_NAME ;
181- let suffix = 0 ;
182- while ( Object . getOwnPropertyDescriptor ( target , canaryName ) ) {
183- suffix += 1 ;
184- canaryName = `${ BASE_CANARY_NAME } _${ suffix } ` ;
185- }
186-
187- Object . defineProperty ( target , canaryName , createCanaryDescriptor ( canaryName ) ) ;
188- installedCanaries . set ( target , canaryName ) ;
189- return canaryName ;
185+ ? ensureCanaryInstalled (
186+ vmContext ,
187+ installedCanaries ,
188+ createCanaryDescriptor ,
189+ )
190+ : ensureCanaryInstalled (
191+ globalThis ,
192+ installedCanaries ,
193+ createCanaryDescriptor ,
194+ ) ;
190195}
191196
192197function createCanaryDescriptor ( canaryName : string ) : PropertyDescriptor {
193198 return {
194199 get ( ) {
195200 const accessStack = captureStack ( ) ;
196- if ( config . shouldReportAccess ( accessStack ) ) {
197- reportAndThrowFinding (
198- buildFindingMessage (
199- "Potential Code Injection (Canary Accessed)" ,
200- `accessed canary: ${ canaryName } ` ,
201- accessStack ,
202- "ignoreAccess" ,
203- "If this is a safe heuristic read, suppress it to continue fuzzing for code execution. Add this to your custom hooks:" ,
204- ) ,
205- false ,
206- ) ;
201+ const pendingAccess = config . shouldReportAccess ( accessStack )
202+ ? {
203+ canaryName,
204+ stack : accessStack ,
205+ handledByInvocation : false ,
206+ }
207+ : undefined ;
208+ if ( pendingAccess ) {
209+ pendingAccesses . push ( pendingAccess ) ;
207210 }
208211
209212 return function canaryCall ( ) {
210213 const invocationStack = captureStack ( ) ;
211214 if ( config . shouldReportInvocation ( invocationStack ) ) {
215+ if ( pendingAccess ) {
216+ pendingAccess . handledByInvocation = true ;
217+ }
212218 reportAndThrowFinding (
213219 buildFindingMessage (
214220 "Confirmed Code Injection (Canary Invoked)" ,
@@ -227,6 +233,24 @@ function createCanaryDescriptor(canaryName: string): PropertyDescriptor {
227233 } ;
228234}
229235
236+ function flushPendingAccesses ( ) : void {
237+ for ( const pendingAccess of pendingAccesses . splice ( 0 ) ) {
238+ if ( pendingAccess . handledByInvocation ) {
239+ continue ;
240+ }
241+ reportFinding (
242+ buildFindingMessage (
243+ "Potential Code Injection (Canary Accessed)" ,
244+ `accessed canary: ${ pendingAccess . canaryName } ` ,
245+ pendingAccess . stack ,
246+ "ignoreAccess" ,
247+ "If this is a safe heuristic read, suppress it to continue fuzzing for code execution. Add this to your custom hooks:" ,
248+ ) ,
249+ false ,
250+ ) ;
251+ }
252+ }
253+
230254function buildFindingMessage (
231255 title : string ,
232256 action : string ,
0 commit comments