Fuzzing Embedded Browser APIs - Part 2: Fooling the Watchdog

In the first part of this series I described how to gain more efficiency in the fuzzing process by moving the fuzzing logic into the browser. This part describes how to overcome watchdogs, which are often found in browsers.

Fuzzing of an API usually takes a while until it is finished. A typical outline of an API fuzzer in JavaScript looks as follows (pseudo code):

for (i = 0; i < func.length; i++) {                     // iterate over functions
    for (j = 0; j < max_params; j++) {                  // iterate over parameters
        for (k = 0; k < testcases.length; k++) {        // iterate over test cases
            params = [default1, default2, ...];
            params[j] = testcase[k];
            func[i].apply(obj, params);
        }
    }
}
alert("Finished!");

Most browsers show a warning message regarding long running JavaScript code after a while and offer to cancel its execution. In normal usage this is desirable behavior, but in fuzzing this can break test runs. Even worse, in the area of embedded rendering engines and browsers, the user has often no choice and the code is simply terminated and there are no chances to perform longer test runs with code built as the above example. This can be avoided by dispersing the nested loops into functions that were called via setTimeout(). Each function represents a layer of the nested loops and keeps it state in a global variable (for the sake of simplicity - there are cleaner solutions). The usage of setTimeout() solves several issues:

  • The watchdog timer is reset to zero and the fuzzing process can last over a very long time.
  • If an API call causes some asynchronous processing, the delay increases the chance for finishing of execution before the next call is initiated. In fact, raising the delay a bit already leaded to additional crashes in the past.
  • UIs gain more responsiveness and update progress display more often.
  • Logging requests have more chances to get through before crashes occur.

The resulting code looks as follows:

var delay = 50;
var i = 0;
var j;
var k;

function loop_functions() {
    if (i < func.length) {
        j = 0;
        setTimeout(loop_params, 0);
    } else {
        alert("Finished!");
    }
}

function loop_params() {
    if (j < max_params) {
        k = 0;
        setTimeout(loop_tests, 0);
    } else {            // next function
        i++;
        setTimeout(loop_functions, 0);
    }
}

function loop_tests() {
    if (j < testcases.length) {
        params = [default1, default2, ...];
        params[j] = testcase[k];
        func[i].apply(obj, params);
        k++;
        setTimeout(loop_tests, delay);
    } else {            // next parameter
        j++;
        setTimeout(loop_params, 0);
    }
}

loop_functions();

Calling setTimeout() with a delay value of zero causes that the fuzzing continues immediately after the JavaScript execution finishes. Further, it makes sense to implement different log levels that differ in loop depths where the logging is done. In the first run the performance can be increased by enabling logging only in the outer loop. Crashes can later be identified by logging more frequently in inner loops.

The next part will describe why and how function parameters are fuzzed type sensitive to increase the effectivity of the fuzzing process.