Use this file to discover all available pages before exploring further.
View on GitHub
Open this notebook in GitHub to run it yourself
This demonstration shows how to harness the power of quantum computers for enhancing software security. Specifically, it uses the quantum Grover algorithm to boost the process of whitebox fuzzing.According to [1], the “killer-app” for whitebox fuzzing is the testing of file and packet parsers. As any vulnerability in such a parser might result in a costly security patch, it is worthwhile investing significant effort to protect the code.
Fuzzing is a dynamic code testing technique that provides random data, known as “fuzz,” to the inputs of a program.The goal is to find bugs, security loopholes, or other unexpected behavior in the software. By feeding the program various input combinations, a fuzzer aims to uncover weaknesses that might be exploited maliciously.
Whitebox fuzzing, in particular, involves accessing the internal structure and code of the program. It combines static and dynamic analysis to not only execute the program with random inputs but also to achieve maximum code coverage, ensuring that all possible execution paths are tested.This allows for more targeted and efficient testing. It usually consists of a “symbolic execution” part: emulating the program to explore various branches and gathering them into a set of constraints.The constraints are solved by a constraint solver, generating new fuzzing input to the program.
This example emphasizes the importance of whitebox testing. First, trigger all code flows for this function:
def foo(x: int, y: int): if x == 12: if y > 3: return "a" return "b" if y + x < 9: if (x % 4) * (y % 2) == 1: return "c" return "d" return "e"
Now (due to simulation limitations) say that x, y are six-bit integers, so they are in the range [0, 63].Try to get all the outputs of foo in a black-box way, e.g., by sampling random inputs:
from collections import Counterimport numpy as npfrom matplotlib import pyplot as pltnp.random.seed(3)x_samples = np.random.randint(0, 63, 500)y_samples = np.random.randint(0, 63, 500)outputs = [foo(x, y) for x, y in zip(x_samples, y_samples)]def plot_outputs(outputs): char_counts = Counter(outputs) # Data for plotting chars = ["a", "b", "c", "d", "e"] counts = list(char_counts.values()) # Plotting plt.bar(char_counts.keys(), char_counts.values()) plt.xlabel("Output") plt.ylabel("Occurrences") plt.show()plot_outputs(outputs)
Note that with 500 inputs, you only reach three of the five different outputs for foo.However, by following the flow of foo, you can generate these constraints to the function:
“a”: (x=12)∧(y>3)
“b”: (x=12)∧(y≤3)
“c”: (x+y<9)∧(x=12)∧((xmod4)×(ymod2)=1)
“d”: (x+y<9)∧(x=12)∧((xmod4)×(ymod2)=1)
“e”: (x=12)∧(x+y<9)
Now, to trigger each of the different outputs, find inputs that satisfy the constraints.Some constraints might not be satisfiable for any input.Although this toy example is easy, the general case, which is an instance of the Constraints Satisfaction Problem (CSP), is computationally hard and belongs to the NP-Complete complexity class.
The physical nature of a quantum computer can be harnessed to generate inputs to the function. Specifically, you can create a ‘superposition’ of all different inputs: a physical state that holds all the possible assignments to the function inputs, for which to compute whether the constraints are fulfilled.The quantum computer allows only a single classical output of the variable.This is where Grover’s algorithm [1,2] is useful: it can generate “good” samples with a high probability, achieving a quadratic(!) speedup over a classical brute force approach.
In the heart of the algorithm, implement an oracle that computes for each state:O |x\rangle =
\begin\{cases\} -|x\rangle & \text\{if \} f(x) = 1 \\
|x\rangle & \text\{otherwise\}
\end\{cases\}Classiq has a built-in arithmetic engine for computing such oracles. Specifically, take the hardest constraint:
“c”: (x+y<9)∧(x=12)∧((xmod4)×(ymod2)=1)
Eliminate the x=12 as it is already satisfied given the first clause, and create an oracle function for it:
The algorithm includes applying a quantum oracle in repetition, such that the probability to sample a good state “rotates” from low to high.Without knowing the concentration of solutions beforehand (which is the common case), one might overshoot with too many repetitions and not arrive at a solution.Fixed Point Amplitude Amplification (FFPA) (4), for example, is a modification to the basic Grover algorithm, which does not suffer from the overshoot issue. However, here, for simplicity, use the basic Grover’s algorithm.Assume that this specific state is only satisfied for a specific input, and calculate the number of oracle repetitions required:
This is indeed ~ the square root of the number of possible assignments: 212 in this case!To save simulation time, simplify even further: use only several Grover repetitions to show that this raises the probability of sampling a “c” input:
Synthesize the circuit using the Classiq synthesis engine.The synthesis takes the high level model definition and creates a quantum circuit implementation within a few seconds:
While “black-box” fuzzing can also potentially benefit from quantum computers, a large quantity of quantum resources is generally required to emulate the state of the classical program. On the other hand, the “white-box” case is lower hanging fruit, requiring fewer resources for a hybrid quantum-classical approach.
This example shows quadratic improvement in comparison to a classical “brute force” solver. However, in reality, there are much faster classical solvers. As a basic example, a solver can “prune” branches of a search by backtracking if a partial assignment is not satisfiable.
Such modifications are, in general, also feasible on a quantum computer.For example, see [5] on Quantum Backtracking.