summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMischa Untaga <99098079+MischaU8@users.noreply.github.com>2024-04-17 12:33:07 +0200
committerMischa Untaga <99098079+MischaU8@users.noreply.github.com>2024-04-17 12:33:07 +0200
commit66c7303fa843c1ba364155c78ca028605f7eb53c (patch)
treef70785272e8eec231cadc2a4a8790a8e58d7d4d4
parentc2d5180f6a6560ed9d21414ea4674793333e293b (diff)
downloadfuzzer-66c7303fa843c1ba364155c78ca028605f7eb53c.tar.gz
detect view mutating state
-rwxr-xr-xrtt-module.js68
1 files changed, 68 insertions, 0 deletions
diff --git a/rtt-module.js b/rtt-module.js
index 8525546..3935c47 100755
--- a/rtt-module.js
+++ b/rtt-module.js
@@ -73,6 +73,8 @@ module.exports.fuzz = function(fuzzerInputData) {
ctx.active = data.pickValue(RULES.roles)
}
+ const state_freeze = JSON.stringify(ctx.state)
+
ctx.view = {}
try {
ctx.view = RULES.view(ctx.state, ctx.active)
@@ -92,6 +94,17 @@ module.exports.fuzz = function(fuzzerInputData) {
return log_crash(new SchemaValidationError("View data fails schema validation: " + rules_view_schema.errors), ctx)
}
+ if (state_freeze !== JSON.stringify(ctx.state)) {
+ let stack
+ try {
+ RULES.view(deep_freeze(ctx.state), ctx.active)
+ } catch (e) {
+ stack = e.stack
+ }
+ let diff_keys = object_keypaths(deep_compare(JSON.parse(state_freeze), ctx.state))
+ return log_crash(new ViewStateMutationError("View mutated state: " + diff_keys.join(", "), stack), ctx)
+ }
+
if (ctx.state.state === 'game_over') {
break
}
@@ -203,6 +216,10 @@ function log_crash(error, ctx, action=undefined, args=undefined) {
line += ` DATA=${data_hash} DUMP=${out_file}`
if (error.message)
line += " MSG=" + JSON.stringify(error.message.replace(/^Error: /, ''))
+ if (error.stack) {
+ const regex = /\/Users\/\w+\/Projects\/rtt\//gi
+ line += " STACK=" + JSON.stringify(error.stack.replace(regex, ''))
+ }
if (!fs.existsSync(out_file)) {
console.log(line)
@@ -217,6 +234,49 @@ function log_crash(error, ctx, action=undefined, args=undefined) {
}
}
+function deep_freeze(object) {
+ // Retrieve the property names defined on object
+ const propNames = Reflect.ownKeys(object);
+ // Freeze properties before freezing self
+ for (const name of propNames) {
+ const value = object[name];
+ if ((value && typeof value === "object") || typeof value === "function") {
+ deep_freeze(value);
+ }
+ }
+ return Object.freeze(object);
+}
+
+function deep_compare(obj1, obj2) {
+ let diffObj = Array.isArray(obj2) ? [] : {}
+ Object.getOwnPropertyNames(obj2).forEach(function(prop) {
+ if (typeof obj2[prop] === 'object') {
+ diffObj[prop] = deep_compare(obj1[prop], obj2[prop])
+ // if it's an array with only length property => empty array => delete
+ // or if it's an object with no own properties => delete
+ if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
+ delete diffObj[prop]
+ }
+ } else if(obj1[prop] !== obj2[prop]) {
+ diffObj[prop] = obj2[prop]
+ }
+ });
+ return diffObj
+}
+
+function object_keypaths(obj, prefix='') {
+ let keys = []
+ for (const key in obj) {
+ if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
+ keys = keys.concat(object_keypaths(obj[key], `${prefix}${key}.`))
+ } else {
+ keys.push(`${prefix}${key}`)
+ }
+ }
+ return keys
+}
+
+
// Custom Error classes, allowing us to ignore expected errors with -x
class UnknownStateError extends Error {
constructor(message) {
@@ -283,6 +343,14 @@ class SchemaValidationError extends Error {
}
}
+class ViewStateMutationError extends Error {
+ constructor(message, stack) {
+ super(message)
+ this.name = "ViewStateMutationError";
+ this.stack = stack
+ }
+}
+
class TimeoutError extends Error {
constructor(message) {
super(message)