Skip to content

Commit

Permalink
Update readme and gates example
Browse files Browse the repository at this point in the history
  • Loading branch information
joeywang4 committed Aug 5, 2024
1 parent b323c7c commit 5cec961
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 169 deletions.
85 changes: 75 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
# Flexo

A compiler for microarchitectural weird machines.
A new design for microarchitectural weird machines, along with a compiler and a proof-of-concept packer application.

For more details, please refer to our paper:

> Ping-Lun Wang, Riccardo Paccagnella, Riad S. Wahby, Fraser Brown.
> "Bending microarchitectural weird machines towards practicality."
> USENIX Security, 2024.
## Supported machines
## Table of contents

The following list is the AWS EC2 instances that we used to run the weird machines generated by Flexo.
- [What are microarchitectural weird machines?](#what-are-microarchitectural-weird-machines)
- [Hardware requirements](#hardware-requirements)
- [Reproduce our results](#reproduce-our-results)
- [Install the Flexo compiler](#install-the-flexo-compiler)
- [Compile a weird machine](#compile-a-weird-machine)
- [Example: basic logic gates](#example-basic-logic-gates)
- [Contacts](#contacts)

## What are microarchitectural weird machines?

Microarchitectural weird machines (µWMs) are code gadgets that perform computation purely through microarchitectural side effects.
They work similarly to a binary circuit.
They use weird registers to store values and use weird gates to compute with them.

For example, here is a weird AND gate with two inputs and one output:

```c
Out[In1[0] + In2[0]]
```

`In1` and `In2` are the two input weird registers, and this AND gate outputs to the weird register `Out`.
Section 2.2 of our paper explains how a µWM works in details.
Note: the code snippets in our paper (Listing 1-5) are for illustrative purposes and can be different from our actual implementation, which we show in Listing 7 in the appendix.

These µWMs can prevent both static and dynamic analysis because they convert computations into memory operations (like the AND gate example above), and debuggers and emulators may not preserve the microarchitecture behavior of a processor, e.g., single-stepping stops transient execution.

Therefore, they are a great candidate for program obfuscation and potentially many other types of attacks.

## Hardware requirements

The following list is the AWS EC2 instances that we used to run our Flexo weird machines.

| Microarchitecture | Instance type | Processor |
| ----------------- | ------------- | ----------------- |
Expand All @@ -26,12 +56,17 @@ The following list is the AWS EC2 instances that we used to run the weird machin
> [!WARNING]
> When using a processor not included in this list, the weird machines may fail to generate correct results.
> While a processor with similar microarchitecture may be able to run our weird machines, we cannot guarantee the accuracy and performance when using other processors.
> For instructions about running our Flexo weird machine on an unsupported processor, please refer to "[Run Flexo on an unsupported processor](#run-flexo-on-an-unsupported-processor)".
## Reproduce our results

Follow the instructions in the [README](reproduce/README.md) file under `reproduce/` to run the experiments in our paper.

## Install with Docker
## Install the Flexo compiler

We suggest installing our Flexo compiler using a Docker or Podman container.
The script below creates a container using the provided [Docker file](./Dockerfile) and runs the [build script](./build.sh) to build the compiler.
For installation using Podman, simply replace `docker` with `podman` in these commands.

```sh
docker build -t flexo .
Expand All @@ -41,16 +76,29 @@ docker run -i -t --rm \
bash -c "cd /flexo && ./build.sh"
```

## Compile a circuit
The compiler will be stored inside the `build/` directory when the installation process is complete.

To compile a circuit, you need to first compile the circuits (a C/C++ program) into LLVM IR.
## Compile a weird machine

### 1. C/C++ to LLVM IR

The Flexo compiler takes a LLVM IR file as input, so the first step of compiling a weird machine is to compile a C/C++ program, which implements the weird machine, into LLVM IR.
The following command uses `clang` (version 17) to compile a C/C++ program into LLVM IR.
Replace `[INPUT_WM_SOURCE]` with the filename of the input C/C++ program and `[LLVM_IR_FILE]` with the filename of the output LLVM IR.

```sh
clang-17 -fno-discard-value-names -fno-inline-functions -O1 -S -emit-llvm [INPUT_CIRCUIT_SOURCE] -o [INPUT_LLVM_IR_FILE]
clang-17 -fno-discard-value-names -fno-inline-functions -O1 -S -emit-llvm [INPUT_WM_SOURCE] -o [LLVM_IR_FILE]
```

After that, run `compile.sh` to execute the Flexo compiler and compile the circuits into weird machines.
The output will be another LLVM IR file.
For more information about how to write a C/C++ program that implements a weird machine, please refer to #TODO.
It is also possible to implement a weird machine using structural Verilog.
For more information about using Verilog to create a weird machine, please refer to #TODO.

### 2. Use the Flexo compiler to generate a weird machine

After that, run `compile.sh` to execute the Flexo compiler and generate the weird machine, which is also in the form of LLVM IR.
The following command performs this step.
Remember to replace `[INPUT_LLVM_IR_FILE]` with the filename of the LLVM IR file generated in the previous step and `[OUTPUT_LLVM_IR_FILE]` with the filename of the output LLVM IR file that contains the weird machine.

```sh
docker run -i -t --rm \
Expand All @@ -59,8 +107,25 @@ docker run -i -t --rm \
bash -c "cd /flexo && ./compile.sh [INPUT_LLVM_IR_FILE] [OUTPUT_LLVM_IR_FILE]"
```

Finally, you can generate an executable file using the output LLVM IR file:
The Flexo compiler provides several compile options to adjust the construction of a weird machine.
For the details of these compiler options, please refer to #TODO

### 3. Generate an executable file

Finally, you can generate an executable file from the output LLVM IR file using the following command.
Remember to replace `[OUTPUT_LLVM_IR_FILE]` with the filename of the LLVM IR file generated from the previous step and `[OUTPUT_EXECUTABLE_FILE]` with the filename of the executable file to be generated.

```sh
clang-17 [OUTPUT_LLVM_IR_FILE] -o [OUTPUT_EXECUTABLE_FILE] -lm -lstdc++
```

## Example: basic logic gates

We implemented several weird machines: basic logic gates, adders, multipliers, a 4-bit ALU, the SHA-1 hash function, the AES block cipher, and the Simon block cipher.
These examples can be found in the [`circuits/`](./circuits/) folder.

The [readme file](./circuits/gates/README.md) under [`circuits/gates`](./circuits/gates/) provides instructions regarding how to build a simple weird machine that computes basic logic gates (`AND`, `OR`, `NOT`, `NAND`, and `MUX`).

## Contacts

Ping-Lun Wang (pinglunw \[at\] andrew \[dot\] cmu \[dot\] edu)
194 changes: 194 additions & 0 deletions circuits/gates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Basic logic gates

This weird machine computes several basic logic gates. Specifically, it computes:

| Operation | #Inputs | #Outputs |
| --------- | ------- | -------- |
| AND | 2 | 1 |
| OR | 2 | 1 |
| NOT | 1 | 1 |
| NAND | 2 | 1 |
| XOR | 2 | 1 |
| MUX | 3 | 1 |
| XOR | 3 | 1 |
| XOR | 4 | 1 |

We first explain [how to compile and run](#compile-the-weird-machine) this weird machine, and then move on to [the construction](#construction-of-this-weird-machine) of the weird machine.

## Compile the weird machine

The following steps are similar to those in "[Compile a weird machine](../../README.md#compile-a-weird-machine)" from the [readme](../../README.md) of Flexo.
Here, we will compile the source code located at [`circuits/gates/test.cpp`](./circuits/gates/test.cpp) and create a simple weird machine.

### 1. C/C++ to LLVM IR

The first step to build this weird machine is to compile the source code into LLVM IR.
A [make file](./circuits/gates/Makefile) is prepared to serve this purpose.
Simply run the `make` command under [`circuits/gates/`](./circuits/gates/) to perform this step.
Note that the make file will use the `clang-17` command to compile the source code.
If `clang-17` is not installed, please use [the Docker/Podman container](#install-the-flexo-compiler) used to install Flexo to perform this step:

```sh
docker run -i -t --rm \
--mount type=bind,source="$(pwd)"/,target=/flexo \
flexo \
make -C /flexo/circuits/gates/
```

This will generate a LLVM IR file, `test.ll`, under [`circuits/gates/`](./circuits/gates/).

### 2. Use the Flexo compiler to generate a weird machine

Next, we invoke the Flexo compiler to generate the weird machine.
We use [the Docker/Podman container](#install-the-flexo-compiler) to perform this step:

```sh
docker run -i -t --rm \
--mount type=bind,source="$(pwd)"/,target=/flexo \
flexo \
bash -c "cd /flexo && ./compile.sh circuits/gates/test.ll circuits/gates/test-wm.ll"
```

This command will output the number of wires and gates of each circuit.
Since this weird machine only contains basic logic gates, the number of gates is always one.
The following is the first few lines of the expected output of this command:

```
Found weird function: _Z12__weird__andbbRb
[Circuit] Wires: 5
[Circuit] Gates: 1
Found weird function: _Z11__weird__orbbRb
[Circuit] Wires: 5
[Circuit] Gates: 1
Found weird function: _Z12__weird__notbRb
[Circuit] Wires: 4
[Circuit] Gates: 1
... // more gates below (omitted)
```

If the compilation process is successful, it will generate another LLVM IR file, `test-wm.ll`, under [`circuits/gates/`](./circuits/gates/) that contains the weird machine.


### 3. Generate an executable file

Finally, we use the LLVM IR file generated by the Flexo compiler to create an executable file.

If the `clang-17` command is available:

```sh
clang-17 ./circuits/gates/test-wm.ll -o ./circuits/gates/test.elf -lm -lstdc++
```

Otherwise, use [the Docker/Podman container](#install-the-flexo-compiler) to perform this step:

```sh
docker run -i -t --rm \
--mount type=bind,source="$(pwd)"/,target=/flexo \
flexo \
clang-17 /flexo/circuits/gates/test-wm.ll -o /flexo/circuits/gates/test.elf -lm -lstdc++
```

After that, `test.elf` will be generated under [`circuits/gates/`](./circuits/gates/).

### 4. Run the weird machine

Simply execute the ELF file to run the weird machine.

```sh
$ ./circuits/gates/test.elf
=== AND gate ===
Accuracy: 94.80200%, Error detected: 5.04200%, Undetected error: 0.15600%
Time usage: 0.764 (us)
over 100000 iterations.
=== OR gate ===
Accuracy: 89.81200%, Error detected: 9.91900%, Undetected error: 0.26900%
Time usage: 0.785 (us)
over 100000 iterations.
=== NOT gate ===
Accuracy: 91.18300%, Error detected: 8.52500%, Undetected error: 0.29200%
Time usage: 0.651 (us)
over 100000 iterations.
... // more gates below (omitted)
```

If the accuracy is very low (less than 70%), then the compiler options provided to the Flexo compiler (step 2) may need to be adjusted, or the processor is not supported.
For more information about adjusting the compiler options for the Flexo compiler, please refer to "[Run Flexo on an unsupported processor](#run-flexo-on-an-unsupported-processor)".

## Construction of this weird machine

The [source code](./test.cpp) of this weird machine has two main components:
1. The weird machine circuits (the functions with the `__weird__` prefix)
2. The measurement code that computes the accuracy and runtime (the `test_acc` function)

Only the weird machine circuits are converted into Flexo weird machines.
This means only the weird machine circuits are executed using microarchitectural side effects, while other functions (including the measurement code) are executed normally using architectural components like a normal C++ program.

### The weird machine circuits

As an example, the `__weird__and` function implements an AND gate with two inputs and one output:

```c
bool __weird__and(bool in1, bool in2, bool& out) {
out = in1 & in2;
return out;
}
```
`in1` and `in2` are the input variables, and the output is stored in `out`.
The return type of this function is `bool`, and the return value contains the error detection result.
Even though this function returns `out`, **the return value does not contain the output value**.
Instead, it returns the **error detection** result.
This means this function returns `true` when it detects an error, and it returns `false` when no error is detected.
### The measurement code
The `test_acc` function runs the weird machine circuits and measures the accuracy and runtime.
The accuracy is defined as the number of runs that the output is correct over the total number of runs.
Detected error rate and undetected error rate have similar definition, so `accuracy` + `error detected` + `undetected error` should always be 100%.
The definition of a correct run, a detected error, and an undetected error is as follows:
Suppose a gate should output the binary value `1`.
There are three cases.
1. If we run this gate and it indeed outputs `1`, then this is a correct run.
2. If we run this gate and it outputs `0`, then this is an undetected error.
3. If we run this gate and it neither outputs `0` nor outputs `1`, then this is a detected error.
When the third case happens (the gates outputs neither `0` nor `1`), the return value of the weird machine circuit will be `true`, which indicates the error detection result, and the output value `out` may contain any value with no useful information.
Here is the measurement code for the AND gate.
```c
test_acc("AND", 2, [](unsigned in) {
bool out;
bool errorDetected = __weird__and(in & 1, (in & 2) >> 1, out);
unsigned result = (errorDetected << 1) | (((in & 3) == 3) != out);
return result;
});
```

The error detection result (the return value of `__weird__and`) is stored in a boolean variable called `errorDetected`, and the output value is stored in `out` (also a `bool`).
It checks whether the output is correct using `((in & 3) == 3) != out`, which is then set as the first bit of `result`.
The second bit of `result` is set to `errorDetected`.

This explains how the main body of `test_acc` works.
In the definition of the `test_acc` function, there is a `for` loop that keep executes the logic gates, and it contains an `if` statement to check whether the gate outputs correctly or not:

```c
if (result == 0) {
++tot_correct_counts;
}
else if (result & 2) {
++tot_detected_counts;
}
else {
++tot_error_counts;
}
```

If `result` is `0`, then it means there is no error detected (second bit unset), and the output is correct (first bit unset).
When the second bit is set (`result & 2`), then this gate detects an error.
Otherwise, this gate outputs incorrectly.

After the `for` loop's execution, this function outputs the average runtime of each iteration (one execution of the gate), and the accuracy, detected error rate, and the undetected error rate.
29 changes: 0 additions & 29 deletions circuits/gates/and.c

This file was deleted.

Loading

0 comments on commit 5cec961

Please sign in to comment.