forked from googleprojectzero/fuzzilli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavaScriptProbeLifting.swift
More file actions
186 lines (161 loc) · 8.05 KB
/
JavaScriptProbeLifting.swift
File metadata and controls
186 lines (161 loc) · 8.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// This file contains the JavaScript specific implementation of the Probe operation. See ProbingMutator.swift for an overview of this feature.
struct JavaScriptProbeHelper {
static let prefixCode = """
// If a sample with this instrumentation crashes, it may need the `fuzzilli` function to reproduce the crash.
if (typeof fuzzilli === 'undefined') fuzzilli = function() {};
const Probe = (function() {
// Note: this code must generally assume that any operation performed on the object to explore, or any object obtained through it (e.g. a prototype), may raise an exception, for example due to triggering a Proxy trap.
// Further, it must also assume that the environment has been modified arbitrarily. For example, the Array.prototype[@@iterator] may have been set to an invalid value, so using `for...of` syntax could trigger an exception.
// Load all necessary routines into local variables as they may be overwritten by the program.
const ProxyConstructor = Proxy;
const ReflectGet = Reflect.get;
const ReflectSet = Reflect.set;
const ReflectHas = Reflect.has;
const hasOwnProperty = Object.hasOwn;
const getPrototypeOf = Object.getPrototypeOf;
const setPrototypeOf = Object.setPrototypeOf;
const stringify = JSON.stringify;
const parseInteger = parseInt;
const execRegExp = Function.prototype.call.bind(RegExp.prototype.exec);
const numberToString = Function.prototype.call.bind(Number.prototype.toString);
const stringStartsWith = Function.prototype.call.bind(String.prototype.startsWith);
const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER;
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
// Action constants.
const PROPERTY_LOAD = "loads";
const PROPERTY_STORE = "stores";
// Property access outcomes.
const PROPERTY_NOT_FOUND = 0;
const PROPERTY_FOUND = 1;
//
// Misc. helper routines.
//
// Helper function to determine if a string is "simple". We only include simple strings for property/method names or string literals.
// A simple string is basically a valid, property name with a maximum length.
const simpleStringRegExp = /^[0-9a-zA-Z_$]+$/;
function isSimpleString(s) {
if (typeof s !== 'string') return false;
return s.length < 50 && execRegExp(simpleStringRegExp, s) !== null;
}
// Helper function to determine if a string is numeric and its numeric value representable as an integer.
function isNumericString(s) {
if (typeof s !== 'string') return false;
let number = parseInteger(s);
return number >= MIN_SAFE_INTEGER && number <= MAX_SAFE_INTEGER && numberToString(number) === s;
}
function isSymbol(s) {
return typeof s === 'symbol';
}
//
// Result recording and reporting.
//
let results = { __proto__: null };
function reportError(msg) {
fuzzilli('FUZZILLI_PRINT', 'PROBING_ERROR: ' + msg);
}
function reportResults() {
fuzzilli('FUZZILLI_PRINT', 'PROBING_RESULTS: ' + stringify(results));
}
// Record a property action performed on a probe.
// |target| is expected to be the original prototype of the probe object. It is used to determine whether the accessed property exists anywhere in the prototype chain of the probe.
function recordAction(action, id, target, key) {
let outcome = PROPERTY_NOT_FOUND;
if (ReflectHas(target, key)) {
outcome = PROPERTY_FOUND;
}
let keyString = key;
if (typeof keyString !== 'string') {
try {
keyString = key.toString();
if (typeof keyString !== 'string') throw 'not a string';
} catch(e) {
// Got some "weird" property key. Ignore it.
return;
}
}
if (!isSimpleString(keyString) && !isNumericString(keyString) && !isSymbol(key)) {
// Cannot deal with this property name. Ignore it.
return;
}
if (isSymbol(key) && !stringStartsWith(keyString, 'Symbol(Symbol.')) {
// We can only deal with well-known symbols (e.g. "Symbol(Symbol.toPrimitive)"), and this isn't one. Ignore it.
return;
}
if (!hasOwnProperty(results, id)) {
results[id] = { [PROPERTY_LOAD]: { __proto__: null }, [PROPERTY_STORE]: { __proto__: null } };
}
// If the same action is performed on the same probe multiple times, we keep the last result.
results[id][action][keyString] = outcome;
}
function recordActionWithErrorHandling(action, id, target, key) {
try {
recordAction(action, id, target, key);
} catch(e) {
reportError(e);
}
}
//
// Probe implementation.
//
function probe(id, value) {
let originalPrototype, newPrototype;
let handler = {
get(target, key, receiver) {
// Special logic to deal with programs that fetch the prototype of an object after it was turned into a probe.
// In that case, the probe Proxy would leak to the script, potentially causing incorrect behaviour. To deal with that,
// we (1) return the original prototype when __proto__ is loaded (but this can be "bypassed" through Object.getPrototypeOf)
// and (2) attempt to detect property accesses on the prototype itself (instead of on the probe) and handle those separately.
if (key === '__proto__' && receiver === value) return originalPrototype;
if (receiver === newPrototype) return ReflectGet(target, key);
recordActionWithErrorHandling(PROPERTY_LOAD, id, target, key);
return ReflectGet(target, key, receiver);
},
set(target, key, value, receiver) {
if (receiver === newPrototype) return ReflectSet(target, key, value);
recordActionWithErrorHandling(PROPERTY_STORE, id, target, key);
return ReflectSet(target, key, value, receiver);
},
has(target, key) {
// Treat this as a load.
recordActionWithErrorHandling(PROPERTY_LOAD, id, target, key);
return ReflectHas(target, key);
},
};
try {
// This can fail, e.g. due to "Cannot convert undefined or null to object" or if the object is non-extensible. In that case, do nothing.
originalPrototype = getPrototypeOf(value);
newPrototype = new ProxyConstructor(originalPrototype, handler);
setPrototypeOf(value, newPrototype);
} catch (e) {}
}
function probeWithErrorHandling(id, value) {
try {
probe(id, value);
} catch(e) {
reportError(e);
}
}
return {
probe: probeWithErrorHandling,
reportResults: reportResults
};
})();
"""
static let probeFunc = "Probe.probe"
static let suffixCode = """
Probe.reportResults();
"""
}