├── .gitmodules ├── src ├── __init__.py ├── tests │ ├── .gitignore │ ├── ct-cond.yaml │ ├── arch-seq.yaml │ ├── ct-bpas-ssbp-patch-off.yaml │ ├── ct-seq-ssbp-patch-off.yaml │ ├── mds.yaml │ ├── model_match.yaml │ ├── rollback_fence_and_expire.yaml │ ├── ct-bpas-n1-ssbp-patch-off.yaml │ ├── generated │ │ ├── bug-cmpxgch-02-06-21.asm │ │ ├── priming-19-03-21.yaml │ │ ├── bug-page-overflow-14-06-21.asm │ │ ├── bug-corrupted-flags-20-03-21.asm │ │ └── priming-19-03-21.asm │ ├── priming.yaml │ ├── wrapper.c │ ├── evaluation │ │ └── impact_of_configuration │ │ │ ├── mds.yaml │ │ │ ├── v1.yaml │ │ │ ├── v4.yaml │ │ │ └── run.sh │ ├── test-detection.yaml │ ├── test-nondetection.yaml │ ├── large_arithmetic.asm │ ├── spectre_v1_independent.asm │ ├── spectre_ret.asm │ ├── priming.asm │ ├── spectre_v1.1.asm │ ├── spectre_v1.asm │ ├── runtests.sh │ ├── spectre_v2.asm │ ├── model_match.asm │ ├── model_flags_match.asm │ ├── spectre_v1_arch.asm │ ├── nops.asm │ ├── lvi.asm │ ├── mds.asm │ ├── direct_jumps.asm │ ├── unittests │ │ ├── asm_basic.asm │ │ ├── unit_analyser.py │ │ ├── unit_isa_loader.py │ │ ├── unit_generators.py │ │ └── unit_model.py │ ├── spectre_v4.asm │ ├── spectre_v4_n2.asm │ ├── calls.asm │ ├── rollback_fence_and_expire.asm │ └── acceptance.bats ├── x86 │ ├── isa_spec │ │ ├── .gitignore │ │ └── get_spec.py │ ├── executor │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── readme.md │ │ ├── main.h │ │ └── measurement.c │ └── tests │ │ ├── unit_executor.py │ │ └── kernel_module.bats ├── custom_conf.yaml ├── input_generator.py ├── cli.py ├── analyser.py ├── isa_loader.py ├── executor.py ├── postprocessor.py ├── config.py ├── service.py ├── coverage.py └── fuzzer.py ├── AUTHORS ├── docs ├── diagrams │ └── Arch.png ├── _main.md ├── architecture.md ├── config.md └── how-revizor-works.md ├── .gitignore ├── LICENSE ├── README.md └── .editorconfig /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.o -------------------------------------------------------------------------------- /src/x86/isa_spec/.gitignore: -------------------------------------------------------------------------------- 1 | *.json -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Oleksii Oleksenko 2 | Boris Koepf -------------------------------------------------------------------------------- /docs/diagrams/Arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sw-contracts/revizor/HEAD/docs/diagrams/Arch.png -------------------------------------------------------------------------------- /src/tests/ct-cond.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - cond 4 | -------------------------------------------------------------------------------- /src/tests/arch-seq.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: arch 2 | contract_execution_clause: 3 | - seq 4 | input_gen_entropy_bits: 4 5 | no_priming: true -------------------------------------------------------------------------------- /src/x86/executor/.gitignore: -------------------------------------------------------------------------------- 1 | .tmp* 2 | *.o 3 | *.cmd 4 | *.symvers 5 | *.order 6 | *.ko 7 | *.mod 8 | *.mod.c 9 | start_qemu.sh 10 | update_module.sh -------------------------------------------------------------------------------- /src/tests/ct-bpas-ssbp-patch-off.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - bpas 4 | enable_ssbp_patch: false 5 | no_priming: true -------------------------------------------------------------------------------- /src/tests/ct-seq-ssbp-patch-off.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - seq 4 | enable_ssbp_patch: false 5 | no_priming: true -------------------------------------------------------------------------------- /src/tests/mds.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - seq 4 | input_gen_entropy_bits: 3 5 | no_priming: true 6 | enable_assist_page: true 7 | -------------------------------------------------------------------------------- /src/tests/model_match.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: l1d 2 | contract_execution_clause: 3 | - seq 4 | enable_ssbp_patch: true 5 | no_priming: true 6 | logging_modes: 7 | - -------------------------------------------------------------------------------- /src/tests/rollback_fence_and_expire.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: l1d 2 | contract_execution_clause: 3 | - cond 4 | logging_modes: 5 | - "fuzzer_trace" 6 | input_gen_seed: 12 7 | -------------------------------------------------------------------------------- /src/tests/ct-bpas-n1-ssbp-patch-off.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - bpas 4 | enable_ssbp_patch: false 5 | model_max_nesting: 1 6 | no_priming: true -------------------------------------------------------------------------------- /src/tests/generated/bug-cmpxgch-02-06-21.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MFENCE # instrumentation 4 | 5 | AND RBX, 0b0111111000000 # instrumentation 6 | CMPXCHG8B qword ptr [R14 + RBX] 7 | 8 | MFENCE # instrumentation 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-*/ 2 | build/ 3 | .vscode/ 4 | .lsync* 5 | venv/ 6 | **/__pycache__/ 7 | CMakeLists.txt 8 | src/x86/isa_spec/*.json 9 | tmp.pdf 10 | src/min.asm 11 | src/tmp.asm 12 | experiments/ 13 | *.code-workspace 14 | src/custom_conf.yaml -------------------------------------------------------------------------------- /src/tests/priming.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - cond 4 | executor_mode: P+P 5 | enable_ssbp_patch: true 6 | enable_assist_page: false 7 | 8 | input_gen_entropy_bits: 2 9 | executor_warmups: 5 10 | no_priming: false 11 | -------------------------------------------------------------------------------- /src/tests/wrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | extern void test_case_main(); 3 | 4 | int main(int argc, const char *argv[]) { 5 | char *p = malloc(4096 * 1024); 6 | p += 512 * 4096; 7 | asm("mov %0, %%r14": "=r" (p)::); 8 | test_case_main(); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /src/tests/generated/priming-19-03-21.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - cond 4 | 5 | executor_mode: P+P 6 | enable_ssbp_patch: true 7 | enable_assist_page: false 8 | 9 | input_gen_entropy_bits: 8 10 | executor_warmups: 5 11 | 12 | no_priming: false 13 | model_max_nesting: 1 14 | -------------------------------------------------------------------------------- /src/tests/evaluation/impact_of_configuration/mds.yaml: -------------------------------------------------------------------------------- 1 | supported_categories: 2 | - BASE-NOP 3 | - BASE-BINARY 4 | - BASE-BITBYTE 5 | - BASE-COND_BR 6 | - BASE-CMOV 7 | - BASE-CONVERT 8 | - BASE-DATAXFER 9 | - BASE-FLAGOP 10 | - BASE-SETCC 11 | - BASE-LOGICAL 12 | - BASE-POP 13 | - BASE-PUSH 14 | contract_observation_clause: ct 15 | contract_execution_clause: 16 | - cond 17 | 18 | enable_ssbp_patch: true 19 | enable_assist_page: true 20 | -------------------------------------------------------------------------------- /src/tests/evaluation/impact_of_configuration/v1.yaml: -------------------------------------------------------------------------------- 1 | supported_categories: 2 | - BASE-NOP 3 | - BASE-BINARY 4 | - BASE-BITBYTE 5 | - BASE-COND_BR 6 | - BASE-CMOV 7 | - BASE-CONVERT 8 | - BASE-DATAXFER 9 | - BASE-FLAGOP 10 | - BASE-SETCC 11 | - BASE-LOGICAL 12 | - BASE-POP 13 | - BASE-PUSH 14 | contract_observation_clause: ct 15 | contract_execution_clause: 16 | - seq 17 | 18 | enable_ssbp_patch: true 19 | enable_assist_page: false 20 | 21 | -------------------------------------------------------------------------------- /src/tests/evaluation/impact_of_configuration/v4.yaml: -------------------------------------------------------------------------------- 1 | supported_categories: 2 | - BASE-NOP 3 | - BASE-BINARY 4 | - BASE-BITBYTE 5 | - BASE-COND_BR 6 | - BASE-CMOV 7 | - BASE-CONVERT 8 | - BASE-DATAXFER 9 | - BASE-FLAGOP 10 | - BASE-SETCC 11 | - BASE-LOGICAL 12 | - BASE-POP 13 | - BASE-PUSH 14 | contract_observation_clause: ct 15 | contract_execution_clause: 16 | - cond 17 | 18 | enable_ssbp_patch: false 19 | enable_assist_page: false 20 | 21 | -------------------------------------------------------------------------------- /src/custom_conf.yaml: -------------------------------------------------------------------------------- 1 | contract_observation_clause: ct 2 | contract_execution_clause: 3 | - cond 4 | # - seq 5 | 6 | enable_ssbp_patch: true 7 | enable_assist_page: false 8 | 9 | feedback_driven_generator: false 10 | input_gen_entropy_bits: 4 11 | 12 | inputs_per_class: 2 13 | 14 | executor_warmups: 40 15 | 16 | # coverage_type: none 17 | # no_priming: true 18 | 19 | # input_gen_seed: 0 20 | 21 | logging_modes: 22 | - info 23 | - fuzzer_debug 24 | # - fuzzer_trace 25 | # - coverage_debug 26 | -------------------------------------------------------------------------------- /src/tests/test-detection.yaml: -------------------------------------------------------------------------------- 1 | supported_categories: 2 | - NOP 3 | - BINARY 4 | - BITBYTE 5 | - COND_BR 6 | - CMOV 7 | - CONVERT 8 | - DATAXFER 9 | - FLAGOP 10 | - SETCC 11 | - LOGICAL 12 | - POP 13 | - PUSH 14 | contract_observation_clause: ct 15 | contract_execution_clause: 16 | - seq 17 | 18 | test_case_size: 16 19 | min_bb_per_function: 5 20 | max_bb_per_function: 5 21 | avg_mem_accesses: 4 22 | input_gen_entropy_bits: 9 23 | 24 | enable_ssbp_patch: true 25 | enable_assist_page: false -------------------------------------------------------------------------------- /src/tests/test-nondetection.yaml: -------------------------------------------------------------------------------- 1 | supported_categories: 2 | - NOP 3 | - BINARY 4 | - BITBYTE 5 | - COND_BR 6 | - CMOV 7 | - CONVERT 8 | - DATAXFER 9 | - FLAGOP 10 | - SETCC 11 | - LOGICAL 12 | - POP 13 | - PUSH 14 | contract_observation_clause: ct 15 | contract_execution_clause: 16 | - cond 17 | 18 | test_case_size: 16 19 | min_bb_per_function: 5 20 | max_bb_per_function: 5 21 | avg_mem_accesses: 4 22 | input_gen_entropy_bits: 3 23 | 24 | enable_ssbp_patch: true 25 | enable_assist_page: false -------------------------------------------------------------------------------- /src/tests/large_arithmetic.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MOV rbx, 1000 4 | .l1: 5 | LFENCE 6 | LEA rax, qword ptr [rax + rax + 8] 7 | LEA rax, qword ptr [rax + rax + 8] 8 | LEA rax, qword ptr [rax + rax + 8] 9 | LEA rax, qword ptr [rax + rax + 8] 10 | LEA rax, qword ptr [rax + rax + 8] 11 | LEA rax, qword ptr [rax + rax + 8] 12 | LEA rax, qword ptr [rax + rax + 8] 13 | LEA rax, qword ptr [rax + rax + 8] 14 | LEA rax, qword ptr [rax + rax + 8] 15 | DEC rbx 16 | JNZ .l1 17 | .l2: 18 | 19 | LFENCE 20 | -------------------------------------------------------------------------------- /src/tests/generated/bug-page-overflow-14-06-21.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | LEA R14, [R14 + 60] # instrumentation 4 | MFENCE # instrumentation 5 | 6 | .test_case_main: 7 | .test_case_main.entry: 8 | JMP .bb0 9 | .bb0: 10 | CMP AX, 26587 11 | {store} SBB DX, DX 12 | 13 | AND RDX, 0b1111111000000 # instrumentation 14 | MUL qword ptr [R14 + RDX] 15 | 16 | AND RDX, 0b1111111000000 # instrumentation 17 | SBB word ptr [R14 + RDX], -30645 18 | 19 | LEA R14, [R14 - 60] # instrumentation 20 | MFENCE # instrumentation 21 | -------------------------------------------------------------------------------- /src/x86/executor/Makefile: -------------------------------------------------------------------------------- 1 | NAME = x86_executor 2 | 3 | ccflags-y += -std=gnu11 -Wno-declaration-after-statement -DL1D_ASSOCIATIVITY=$(shell cat /sys/devices/system/cpu/cpu0/cache/index0/ways_of_associativity) 4 | 5 | obj-m += $(NAME).o 6 | $(NAME)-objs += main.o templates.o measurement.o 7 | 8 | 9 | KDIR=/lib/modules/$(shell uname -r)/build 10 | # KDIR=/home/t-oleksenkoo/kernel/linux 11 | 12 | all: 13 | make -C $(KDIR) M=$(PWD) modules 14 | 15 | clean: 16 | make -C $(KDIR) M=$(PWD) clean 17 | 18 | install: 19 | sudo insmod $(NAME).ko 20 | 21 | uninstall: 22 | sudo rmmod $(NAME) -------------------------------------------------------------------------------- /src/x86/executor/readme.md: -------------------------------------------------------------------------------- 1 | # Install 2 | Tested on Linux v5.6.6-300 and v5.6.13-100. 3 | No guarantees about other versions. 4 | 5 | To build the executor, run: 6 | 7 | ``` 8 | make uninstall 9 | make clean 10 | make 11 | make install 12 | ``` 13 | 14 | # Using the executor 15 | 16 | Use the Revizor CLI (`src/cli.py`). 17 | This executor is not meant to be used standalone. 18 | 19 | On your own peril, you could try using it directly, through the `/sys/x86_executor/` pseudo file system. 20 | You can find an example of how to use it in `src/x86/tests/run.bats`. 21 | But I promise you, there will come a point when your machine will crash or hang. 22 | Better not. 23 | -------------------------------------------------------------------------------- /src/tests/spectre_v1_independent.asm: -------------------------------------------------------------------------------- 1 | # This test case is identical to spectre_v1 except the offset of the speculative mem. access 2 | # is input-independent. Therefore, this test case must not be flagged. 3 | 4 | .intel_syntax noprefix 5 | .test_case_enter: 6 | MOV rcx, r14 7 | 8 | # input: ebx - a random value, eax - fixed value 9 | MOV rax, 128 10 | LFENCE 11 | 12 | # no delay to increase the likelihood of a false positive 13 | SHL rbx, 63 14 | SHR rbx, 63 15 | 16 | # speculation 17 | CMP rbx, 0 18 | JE .l1 19 | .l0: 20 | # rbx != 0 21 | MOV rcx, qword ptr [rcx + rax] 22 | JMP .l2 23 | .l1: 24 | # rbx == 0 25 | MOV rcx, qword ptr [rcx] 26 | .l2: 27 | MFENCE 28 | -------------------------------------------------------------------------------- /docs/_main.md: -------------------------------------------------------------------------------- 1 | # Explanations 2 | 3 | * [How Revizor Works](./how-revizor-works.md) 4 | * [Revizor Architecture](./architecture.md) 5 | 6 | # Papers 7 | 8 | * [Hardware-Software Contracts for Secure Speculation](https://arxiv.org/abs/2006.03841) 9 | * [Revizor: Testing Black-box CPUs against Speculation Contracts](https://arxiv.org/abs/2105.06872) 10 | 11 | # How-To Guides 12 | 13 | * [Black-box Testing Strategy](./testing-strategy.md) 14 | * [Adding New Architecture](./adding-new-architecture.md) 15 | 16 | # References 17 | 18 | * [Command-line Interface](./cli.md) 19 | * [Configuration File](./config.md) 20 | * [Key Modules and Interfaces](./modules.md) 21 | 22 | # Tutorials 23 | 24 | None so far -------------------------------------------------------------------------------- /src/tests/spectre_ret.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | 4 | # speculative offset: 5 | # these shifts generate a random page offset, 64-bit aligned 6 | AND rax, 0b111111000000 7 | LFENCE 8 | 9 | MOV rcx, r14 10 | ADD rsp, 8 # ensure that the CALL and RET use the first cache set 11 | 12 | CALL .function_1 13 | 14 | .unreachable: 15 | // LFENCE # if you uncomment this line, the speculation will stop 16 | AND rax, 0b110000000 # reduce the number of possibilities 17 | MOV rax, qword ptr [rcx + rax] # speculative access 18 | LFENCE 19 | 20 | .function_1: 21 | LEA rdx, qword ptr [rip + .function_2] 22 | MOV qword ptr [rsp], rdx 23 | RET 24 | 25 | .function_2: 26 | MOV rdx, qword ptr [rcx + 64] 27 | MFENCE 28 | -------------------------------------------------------------------------------- /src/tests/priming.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MFENCE 4 | NOP 5 | NOP 6 | NOP 7 | NOP 8 | NOP 9 | NOP 10 | ADD R14, 40 11 | JMP .bb0 12 | .bb0: 13 | AND RCX, 0b111111000000 14 | ADD RCX, R14 15 | CMOVZ ECX, dword ptr [RCX] 16 | LAHF 17 | ADC RAX, RAX 18 | AND RDX, 0b111111000000 19 | ADD RDX, R14 20 | SETNZ byte ptr [RDX] 21 | MOVSX EDX, BX 22 | AND RCX, 0b111111000000 23 | ADD RCX, R14 24 | SUB CX, 19187 25 | JNP .bb1 26 | JMP .bb2 27 | .bb1: 28 | MOVZX EDX, CX 29 | DEC AL 30 | ADC RBX, RBX 31 | CMOVBE EAX, EAX 32 | OR DL, DL 33 | SETB BL 34 | JS .bb2 35 | JMP .bb3 36 | .bb2: 37 | AND RDX, 0b111111000000 38 | ADD RDX, R14 39 | ADD qword ptr [RDX], 621805592 40 | .bb3: 41 | AND RBX, 0b111111000000 42 | ADD RBX, R14 43 | SUB R14, 40 44 | MFENCE 45 | -------------------------------------------------------------------------------- /src/tests/spectre_v1.1.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | LFENCE 4 | 5 | # reduce the entropy of rax 6 | AND rax, 0b111111000000 7 | 8 | # delay the cond. jump 9 | MOV r15, 0 10 | LEA rbx, qword ptr [rbx + r15 + 1] 11 | LEA rbx, qword ptr [rbx + r15 - 1] 12 | LEA rbx, qword ptr [rbx + r15 + 1] 13 | LEA rbx, qword ptr [rbx + r15 - 1] 14 | LEA rbx, qword ptr [rbx + r15 + 1] 15 | LEA rbx, qword ptr [rbx + r15 - 1] 16 | LEA rbx, qword ptr [rbx + r15 + 1] 17 | LEA rbx, qword ptr [rbx + r15 - 1] 18 | LEA rbx, qword ptr [rbx + r15 + 1] 19 | LEA rbx, qword ptr [rbx + r15 - 1] 20 | 21 | # reduce the entropy in rbx 22 | AND rbx, 0b1000000 23 | 24 | CMP rbx, 0 25 | JE .l1 # misprediction 26 | .l0: 27 | # rbx != 0 28 | MOV qword ptr [r14], rax 29 | MOV rbx, qword ptr [r14] 30 | MOV rbx, qword ptr [r14 + rbx] 31 | .l1: 32 | MFENCE 33 | -------------------------------------------------------------------------------- /src/tests/spectre_v1.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | LFENCE 4 | 5 | # reduce the entropy of rax 6 | AND rax, 0b111111000000 7 | 8 | # delay the cond. jump 9 | LEA rbx, qword ptr [rbx + rax + 1] 10 | LEA rbx, qword ptr [rbx + rax + 1] 11 | LEA rbx, qword ptr [rbx + rax + 1] 12 | LEA rbx, qword ptr [rbx + rax + 1] 13 | LEA rbx, qword ptr [rbx + rax + 1] 14 | LEA rbx, qword ptr [rbx + rax + 1] 15 | LEA rbx, qword ptr [rbx + rax + 1] 16 | LEA rbx, qword ptr [rbx + rax + 1] 17 | LEA rbx, qword ptr [rbx + rax + 1] 18 | LEA rbx, qword ptr [rbx + rax + 1] 19 | 20 | # reduce the entropy in rbx 21 | AND rbx, 0b1000000 22 | 23 | CMP rbx, 0 24 | JE .l1 # misprediction 25 | .l0: 26 | # rbx != 0 27 | MOV rax, qword ptr [r14 + rax] 28 | JMP .l2 29 | .l1: 30 | # rbx == 0 31 | #MOV rax, qword ptr [r14 + 64] 32 | .l2: 33 | MFENCE 34 | -------------------------------------------------------------------------------- /src/tests/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 4 | 5 | echo "" 6 | echo "===== Type Checking with mypy =====" 7 | echo "" 8 | cd $SCRIPT_DIR/.. || exit 9 | python3 -m mypy cli.py --ignore-missing-imports 10 | cd - > /dev/null || exit 11 | 12 | echo "" 13 | echo "===== Core Unit Tests =====" 14 | cd $SCRIPT_DIR || exit 15 | python3 -m unittest discover unittests -p "unit_*.py" -v 16 | cd - > /dev/null || exit 17 | 18 | echo "" 19 | echo "===== x86 tests =====" 20 | echo "" 21 | cd $SCRIPT_DIR/../x86 || exit 22 | ./tests/kernel_module.bats 23 | echo "x86 unittests" 24 | python3 -m unittest discover tests -p "unit_*.py" -v 25 | cd - || exit 26 | 27 | echo "" 28 | echo "===== Acceptance Tests =====" 29 | echo "" 30 | cd $SCRIPT_DIR/.. || exit 31 | ./tests/acceptance.bats 32 | cd - || exit -------------------------------------------------------------------------------- /src/tests/generated/bug-corrupted-flags-20-03-21.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MFENCE 4 | ADD R14, 59 5 | test_case_main: 6 | .test_case_main.entry: 7 | JMP .bb0 8 | .bb0: 9 | LFENCE 10 | {store} REX OR DL, DL 11 | LFENCE 12 | AND AL, 61 13 | LFENCE 14 | {disp32} JNO .bb1 15 | LFENCE 16 | JMP .bb2 17 | LFENCE 18 | .bb1: 19 | LFENCE 20 | LAHF 21 | LFENCE 22 | AND RBX, 0b111111000000 23 | LFENCE 24 | ADD RBX, R14 25 | LFENCE 26 | .bb2: 27 | LFENCE 28 | AND RDX, 0b111111000000 29 | LFENCE 30 | ADD RDX, R14 31 | LFENCE 32 | AND RCX, 0b111111000000 33 | LFENCE 34 | ADD RCX, R14 35 | LFENCE 36 | AND RCX, 0b111111000000 37 | LFENCE 38 | ADD RCX, R14 39 | LFENCE 40 | AND RCX, 0b111111000000 41 | LFENCE 42 | ADD RCX, R14 43 | LFENCE 44 | AND RAX, 0b111111000000 45 | LFENCE 46 | ADD RAX, R14 47 | LFENCE 48 | LOCK SBB byte ptr [RAX], BL 49 | SUB R14, 59 50 | -------------------------------------------------------------------------------- /src/tests/spectre_v2.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | LFENCE 3 | .test_case_enter: 4 | 5 | # reduce the entropy of rax 6 | AND rax, 0b111111000000 7 | 8 | # prepare jump targets 9 | LEA rdx, qword ptr [rip + .l1] 10 | LEA rsi, qword ptr [rip + .l2] 11 | 12 | # delay the jump 13 | LEA rbx, qword ptr [rbx + rax + 1] 14 | LEA rbx, qword ptr [rbx + rax + 1] 15 | LEA rbx, qword ptr [rbx + rax + 1] 16 | LEA rbx, qword ptr [rbx + rax + 1] 17 | LEA rbx, qword ptr [rbx + rax + 1] 18 | LEA rbx, qword ptr [rbx + rax + 1] 19 | LEA rbx, qword ptr [rbx + rax + 1] 20 | LEA rbx, qword ptr [rbx + rax + 1] 21 | LEA rbx, qword ptr [rbx + rax + 1] 22 | LEA rbx, qword ptr [rbx + rax + 1] 23 | 24 | # reduce the entropy in rbx 25 | AND rbx, 0b1000000 26 | 27 | # select a target based on the random value in rbx 28 | CMP rbx, 0 29 | CMOVE rsi, rdx 30 | 31 | JMP rsi # misprediction 32 | .l1: 33 | # rbx = 0 34 | MOV rdx, qword ptr [r14 + rax] 35 | .l2: 36 | MFENCE 37 | -------------------------------------------------------------------------------- /src/tests/model_match.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | 4 | # test register values 5 | AND rax, 0b111111000000 6 | MOV rax, qword ptr [r14 + rax] 7 | 8 | AND rbx, 0b111111000000 9 | MOV rbx, qword ptr [r14 + rbx] 10 | 11 | AND rcx, 0b111111000000 12 | MOV rcx, qword ptr [r14 + rcx] 13 | 14 | AND rdx, 0b111111000000 15 | MOV rdx, qword ptr [r14 + rdx] 16 | 17 | AND rsi, 0b111111000000 18 | MOV rsi, qword ptr [r14 + rsi] 19 | 20 | AND rdi, 0b111111000000 21 | MOV rdi, qword ptr [r14 + rdi] 22 | 23 | MOV rax, rsp 24 | AND rax, 0b111111000000 25 | MOV rax, qword ptr [r14 + rax] 26 | 27 | # test values in memory 28 | MOV rax, qword ptr [r14] 29 | AND rax, 0b111111000000 30 | MOV rax, qword ptr [r14 + rax] 31 | 32 | MOV rax, qword ptr [r14 + 1024] 33 | AND rax, 0b111111000000 34 | MOV rax, qword ptr [r14 + rax] 35 | 36 | MOV rax, qword ptr [r14 + 4096 - 8] 37 | AND rax, 0b111111000000 38 | MOV rax, qword ptr [r14 + rax] 39 | 40 | MFENCE 41 | -------------------------------------------------------------------------------- /src/tests/model_flags_match.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | LFENCE 4 | MOV rax, r14 5 | 6 | MOV rbx, 0 7 | MOV rcx, 64 8 | CMOVB rbx, rcx 9 | MOV rcx, qword ptr [rax + rbx] 10 | 11 | MOV rbx, 0 12 | MOV rcx, 128 13 | CMOVBE rbx, rcx 14 | MOV rcx, qword ptr [rax + rbx] 15 | 16 | MOV rbx, 0 17 | MOV rcx, 192 18 | CMOVL rbx, rcx 19 | MOV rcx, qword ptr [rax + rbx] 20 | 21 | MOV rbx, 0 22 | MOV rcx, 256 23 | CMOVLE rbx, rcx 24 | MOV rcx, qword ptr [rax + rbx] 25 | 26 | MOV rbx, 0 27 | MOV rcx, 320 28 | CMOVO rbx, rcx 29 | MOV rcx, qword ptr [rax + rbx] 30 | 31 | MOV rbx, 0 32 | MOV rcx, 384 33 | CMOVP rbx, rcx 34 | MOV rcx, qword ptr [rax + rbx] 35 | 36 | MOV rbx, 0 37 | MOV rcx, 448 38 | CMOVS rbx, rcx 39 | MOV rcx, qword ptr [rax + rbx] 40 | 41 | MOV rbx, 0 42 | MOV rcx, 512 43 | CMOVZ rbx, rcx 44 | MOV rcx, qword ptr [rax + rbx] 45 | 46 | // CMOVNB 47 | // CMOVNBE 48 | // CMOVNL 49 | // CMOVNLE 50 | // CMOVNO 51 | // CMOVNP 52 | // CMOVNS 53 | // CMOVNZ 54 | MFENCE 55 | -------------------------------------------------------------------------------- /src/tests/spectre_v1_arch.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | LFENCE 4 | 5 | # delay the cond. jump 6 | MOV rax, 0 7 | LEA rbx, qword ptr [rbx + rax + 1] 8 | LEA rbx, qword ptr [rbx + rax - 1] 9 | LEA rbx, qword ptr [rbx + rax + 1] 10 | LEA rbx, qword ptr [rbx + rax - 1] 11 | LEA rbx, qword ptr [rbx + rax + 1] 12 | LEA rbx, qword ptr [rbx + rax - 1] 13 | LEA rbx, qword ptr [rbx + rax + 1] 14 | LEA rbx, qword ptr [rbx + rax - 1] 15 | LEA rbx, qword ptr [rbx + rax + 1] 16 | LEA rbx, qword ptr [rbx + rax - 1] 17 | LEA rbx, qword ptr [rbx + rax + 1] 18 | LEA rbx, qword ptr [rbx + rax - 1] 19 | LEA rbx, qword ptr [rbx + rax + 1] 20 | LEA rbx, qword ptr [rbx + rax - 1] 21 | 22 | # reduce the entropy in rbx 23 | AND rbx, 0b1000000 24 | 25 | CMP rbx, 0 26 | JE .l1 # misprediction 27 | .l0: 28 | # rbx != 0 29 | MOV rax, qword ptr [r14 + 1024] 30 | SHL rax, 2 31 | AND rax, 0b111111000000 32 | MOV rax, qword ptr [r14 + rax] # leakage happens here 33 | .l1: 34 | 35 | MFENCE 36 | -------------------------------------------------------------------------------- /src/tests/nops.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | NOP 4 | NOP 5 | NOP 6 | NOP 7 | NOP 8 | NOP 9 | NOP 10 | NOP 11 | NOP 12 | NOP 13 | NOP 14 | NOP 15 | NOP 16 | NOP 17 | NOP 18 | NOP 19 | NOP 20 | NOP 21 | NOP 22 | NOP 23 | NOP 24 | NOP 25 | NOP 26 | NOP 27 | NOP 28 | NOP 29 | NOP 30 | NOP 31 | NOP 32 | NOP 33 | NOP 34 | NOP 35 | NOP 36 | NOP 37 | NOP 38 | NOP 39 | NOP 40 | NOP 41 | NOP 42 | NOP 43 | NOP 44 | NOP 45 | NOP 46 | NOP 47 | NOP 48 | NOP 49 | NOP 50 | NOP 51 | NOP 52 | NOP 53 | NOP 54 | NOP 55 | NOP 56 | NOP 57 | NOP 58 | NOP 59 | NOP 60 | NOP 61 | NOP 62 | NOP 63 | NOP 64 | NOP 65 | NOP 66 | NOP 67 | NOP 68 | NOP 69 | NOP 70 | NOP 71 | NOP 72 | NOP 73 | NOP 74 | NOP 75 | NOP 76 | NOP 77 | NOP 78 | NOP 79 | NOP 80 | NOP 81 | NOP 82 | NOP 83 | NOP 84 | NOP 85 | NOP 86 | NOP 87 | NOP 88 | NOP 89 | NOP 90 | NOP 91 | NOP 92 | NOP 93 | NOP 94 | NOP 95 | NOP 96 | NOP 97 | NOP 98 | NOP 99 | NOP 100 | NOP 101 | NOP 102 | -------------------------------------------------------------------------------- /src/tests/lvi.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | AND rax, 0b111111111111 # keep the mem. access within the sandbox 4 | #MOV rax, 46 5 | MFENCE 6 | 7 | # put a value into store buffer; repeated several times to make sure we get a hit 8 | MOV qword ptr [r14], rax 9 | MOV rax, qword ptr [r14] 10 | SFENCE 11 | MOV qword ptr [r14], rax 12 | MOV rax, qword ptr [r14] 13 | SFENCE 14 | MOV qword ptr [r14], rax 15 | MOV rax, qword ptr [r14] 16 | SFENCE 17 | MOV qword ptr [r14], rax 18 | MOV rax, qword ptr [r14] 19 | SFENCE 20 | MOV qword ptr [r14], rax 21 | MOV rax, qword ptr [r14] 22 | SFENCE 23 | MOV qword ptr [r14], rax 24 | MOV rax, qword ptr [r14] 25 | SFENCE 26 | MOV qword ptr [r14], rax 27 | MOV rax, qword ptr [r14] 28 | SFENCE 29 | MOV qword ptr [r14], rax 30 | MOV rax, qword ptr [r14] 31 | SFENCE 32 | 33 | MOV qword ptr [r14], rax 34 | MOV rax, qword ptr [r14] 35 | 36 | # Read from a non-accessed address thus triggerring microcode assist 37 | ADD rcx, qword ptr [r14 + 4096] 38 | //SHL rcx, 6 39 | 40 | # dependent load 41 | #LFENCE 42 | AND rcx, 0b111111000000 43 | MOV rdx, qword ptr [r14 + rcx] 44 | 45 | MFENCE 46 | -------------------------------------------------------------------------------- /src/tests/mds.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | AND rax, 0b111111111111 # keep the mem. access within the sandbox 4 | #MOV rax, 46 5 | MFENCE 6 | 7 | # put a value into store buffer; repeated several times to make sure we get a hit 8 | MOV qword ptr [r14], rax 9 | MOV rax, qword ptr [r14] 10 | SFENCE 11 | MOV qword ptr [r14], rax 12 | MOV rax, qword ptr [r14] 13 | SFENCE 14 | MOV qword ptr [r14], rax 15 | MOV rax, qword ptr [r14] 16 | SFENCE 17 | MOV qword ptr [r14], rax 18 | MOV rax, qword ptr [r14] 19 | SFENCE 20 | MOV qword ptr [r14], rax 21 | MOV rax, qword ptr [r14] 22 | SFENCE 23 | MOV qword ptr [r14], rax 24 | MOV rax, qword ptr [r14] 25 | SFENCE 26 | MOV qword ptr [r14], rax 27 | MOV rax, qword ptr [r14] 28 | SFENCE 29 | MOV qword ptr [r14], rax 30 | MOV rax, qword ptr [r14] 31 | SFENCE 32 | 33 | MOV qword ptr [r14], rax 34 | MOV rax, qword ptr [r14] 35 | 36 | # Read from a non-accessed address thus triggerring microcode assist 37 | ADD rcx, qword ptr [r14 + 4096] 38 | #SHL rcx, 6 39 | 40 | # dependent load 41 | #LFENCE 42 | AND rcx, 0b111111000000 43 | MOV rdx, qword ptr [r14 + rcx] 44 | 45 | MFENCE 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Oleksii Oleksenko. 4 | Copyright (c) Microsoft Corporation. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE 23 | -------------------------------------------------------------------------------- /src/tests/direct_jumps.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | JMP .1 4 | .1: 5 | JMP .2 6 | .2: 7 | JMP .3 8 | .3: 9 | JMP .4 10 | .4: 11 | JMP .5 12 | .5: 13 | JMP .6 14 | .6: 15 | JMP .7 16 | .7: 17 | JMP .8 18 | .8: 19 | JMP .9 20 | .9: 21 | JMP .10 22 | .10: 23 | JMP .11 24 | .11: 25 | JMP .12 26 | .12: 27 | JMP .13 28 | .13: 29 | JMP .14 30 | .14: 31 | JMP .15 32 | .15: 33 | JMP .16 34 | .16: 35 | JMP .17 36 | .17: 37 | JMP .18 38 | .18: 39 | JMP .19 40 | .19: 41 | JMP .20 42 | .20: 43 | JMP .21 44 | .21: 45 | JMP .22 46 | .22: 47 | JMP .23 48 | .23: 49 | JMP .24 50 | .24: 51 | JMP .25 52 | .25: 53 | JMP .26 54 | .26: 55 | JMP .27 56 | .27: 57 | JMP .28 58 | .28: 59 | JMP .29 60 | .29: 61 | JMP .30 62 | .30: 63 | JMP .31 64 | .31: 65 | JMP .32 66 | .32: 67 | JMP .33 68 | .33: 69 | JMP .34 70 | .34: 71 | JMP .35 72 | .35: 73 | JMP .36 74 | .36: 75 | JMP .37 76 | .37: 77 | JMP .38 78 | .38: 79 | JMP .39 80 | .39: 81 | JMP .40 82 | .40: 83 | JMP .41 84 | .41: 85 | JMP .42 86 | .42: 87 | JMP .43 88 | .43: 89 | JMP .44 90 | .44: 91 | JMP .45 92 | .45: 93 | JMP .46 94 | .46: 95 | JMP .47 96 | .47: 97 | JMP .48 98 | .48: 99 | JMP .49 100 | .49: 101 | JMP .50 102 | .50: 103 | -------------------------------------------------------------------------------- /src/tests/unittests/asm_basic.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | LEA R14, [R14 + 40] # instrumentation 3 | MFENCE # instrumentation 4 | .test_case_enter: 5 | 6 | .function_main: 7 | .bb_main.entry: 8 | JMP .bb_main.0 9 | .bb_main.0: 10 | 11 | # line with a comment 12 | NOP # no operands 13 | DIV RBX # one operand 14 | AND RAX, RAX # two operands 15 | AND RAX, 0b0111111000000 # immediate value - binary 16 | AND RAX, 42 # immediate value - decimal 17 | AND RAX, 0xfa # immediate value - hex 18 | AND RAX, -1 # immediate value - negative 19 | AND RDI, R14 # reserved register 20 | neg rax # lowercase 21 | MOV RAX, qword ptr [R14] # load - simple addressing 22 | MOV RAX, qword ptr [R14 + RBX] # load - two parts 23 | MOV RAX, qword ptr [R14 + RBX + 8] # load - three parts 24 | MOV RAX, qword ptr [R14 + RBX] # store 25 | LOCK ADC dword ptr [R14 + RBX], EAX # lock prefix 26 | AND RAX, RAX # instrumentation 27 | 28 | MOV RDI, RDI # multiple matches 29 | 30 | 31 | JMP .bb_main.1 32 | .bb_main.1: 33 | AND RDI, 0b0111111000000 # indentation 34 | CMP qword ptr [ R14 + RDI ] , 59 # extra spaces 35 | AND RDI, 0b0111111000000 # instrumentation 36 | CMPXCHG byte ptr [R14 + RSI], SIL 37 | 38 | .bb_main.exit: 39 | .test_case_exit: 40 | MFENCE # instrumentation 41 | LEA R14, [R14 - 40] # instrumentation 42 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | ![architecture](Arch.png) 4 | 5 | **Under construction** 6 | 7 | [comment]: <> (## Instruction Set Spec) 8 | 9 | [comment]: <> (This XML file: https://www.uops.info/xml.html originating from Intel XED (https://intelxed.github.io/)) 10 | 11 | [comment]: <> (Received from: `--instruction-set` (or `-s`) CLI argument.) 12 | 13 | [comment]: <> (Passed down to: `Generator.__init__`) 14 | 15 | 16 | [comment]: <> (## Generator Initializer) 17 | 18 | [comment]: <> (None so far.) 19 | 20 | [comment]: <> (In future, may include test case templates, grammar, etc.) 21 | 22 | [comment]: <> (## Test Case) 23 | 24 | [comment]: <> (An assembly file. Currently, in Intel syntax.) 25 | 26 | [comment]: <> (Received from: `self.generator.create_test_case()` + `self.generator.materialize(filename)`) 27 | 28 | [comment]: <> (Passed down to: `model.load_test_case` and `executor.load_test_case`) 29 | 30 | 31 | [comment]: <> (## Inputs) 32 | 33 | [comment]: <> (Currently, each input is a single 32-bit integer, used later as a PRNG seed inside the test case to initialize memory and registers.) 34 | 35 | [comment]: <> (Inputs are generated in batches; that is, Input Generator returns `List[int]`.) 36 | 37 | [comment]: <> (Received from: `input_gen.generate(...)`) 38 | 39 | [comment]: <> (Passed down to: `model.trace_test_case(inputs)` and `executor.trace_test_case(inputs)`.) 40 | -------------------------------------------------------------------------------- /src/tests/spectre_v4.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MOV rax, 0 4 | 5 | # the leaked value - rcx 6 | # construct a page offset from the random value 7 | AND rcx, 0b111111000000 8 | ADD rcx, 64 9 | 10 | # save some value into the test address 11 | MOV qword ptr [r14], rcx 12 | MFENCE 13 | 14 | # delay the store 15 | LEA rbx, qword ptr [rbx + rax + 1] 16 | LEA rbx, qword ptr [rbx + rax - 1] 17 | LEA rbx, qword ptr [rbx + rax + 1] 18 | LEA rbx, qword ptr [rbx + rax - 1] 19 | LEA rbx, qword ptr [rbx + rax + 1] 20 | LEA rbx, qword ptr [rbx + rax - 1] 21 | LEA rbx, qword ptr [rbx + rax + 1] 22 | LEA rbx, qword ptr [rbx + rax - 1] 23 | LEA rbx, qword ptr [rbx + rax + 1] 24 | LEA rbx, qword ptr [rbx + rax - 1] 25 | LEA rbx, qword ptr [rbx + rax + 1] 26 | LEA rbx, qword ptr [rbx + rax - 1] 27 | LEA rbx, qword ptr [rbx + rax + 1] 28 | LEA rbx, qword ptr [rbx + rax - 1] 29 | LEA rbx, qword ptr [rbx + rax + 1] 30 | LEA rbx, qword ptr [rbx + rax - 1] 31 | LEA rbx, qword ptr [rbx + rax + 1] 32 | LEA rbx, qword ptr [rbx + rax - 1] 33 | LEA rbx, qword ptr [rbx + rax + 1] 34 | LEA rbx, qword ptr [rbx + rax - 1] 35 | LEA rbx, qword ptr [rbx + rax + 1] 36 | LEA rbx, qword ptr [rbx + rax - 1] 37 | LEA rbx, qword ptr [rbx + rax + 1] 38 | LEA rbx, qword ptr [rbx + rax - 1] 39 | LEA rbx, qword ptr [rbx + rax + 1] 40 | LEA rbx, qword ptr [rbx + rax - 1] 41 | 42 | # store and load, potentially matching 43 | AND rbx, 0b111111000000 44 | MOV qword ptr [r14 + rbx], 4096 - 64 45 | MOV rdx, qword ptr [r14] # misprediction happens here 46 | 47 | # dependent load 48 | MOV rdx, qword ptr [r14 + rdx] 49 | MFENCE 50 | -------------------------------------------------------------------------------- /src/tests/spectre_v4_n2.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | MOV rax, 0 4 | 5 | # the leaked value - rcx 6 | # construct a page offset from the random value 7 | AND rcx, 0b111111000000 8 | ADD rcx, 64 9 | 10 | # save some value into the test address 11 | MOV qword ptr [r14], rcx 12 | MFENCE 13 | 14 | # delay the store 15 | LEA rbx, qword ptr [rbx + rax + 1] 16 | LEA rbx, qword ptr [rbx + rax - 1] 17 | LEA rbx, qword ptr [rbx + rax + 1] 18 | LEA rbx, qword ptr [rbx + rax - 1] 19 | LEA rbx, qword ptr [rbx + rax + 1] 20 | LEA rbx, qword ptr [rbx + rax - 1] 21 | LEA rbx, qword ptr [rbx + rax + 1] 22 | LEA rbx, qword ptr [rbx + rax - 1] 23 | LEA rbx, qword ptr [rbx + rax + 1] 24 | LEA rbx, qword ptr [rbx + rax - 1] 25 | LEA rbx, qword ptr [rbx + rax + 1] 26 | LEA rbx, qword ptr [rbx + rax - 1] 27 | LEA rbx, qword ptr [rbx + rax + 1] 28 | LEA rbx, qword ptr [rbx + rax - 1] 29 | LEA rbx, qword ptr [rbx + rax + 1] 30 | LEA rbx, qword ptr [rbx + rax - 1] 31 | LEA rbx, qword ptr [rbx + rax + 1] 32 | LEA rbx, qword ptr [rbx + rax - 1] 33 | LEA rbx, qword ptr [rbx + rax + 1] 34 | LEA rbx, qword ptr [rbx + rax - 1] 35 | LEA rbx, qword ptr [rbx + rax + 1] 36 | LEA rbx, qword ptr [rbx + rax - 1] 37 | LEA rbx, qword ptr [rbx + rax + 1] 38 | LEA rbx, qword ptr [rbx + rax - 1] 39 | LEA rbx, qword ptr [rbx + rax + 1] 40 | LEA rbx, qword ptr [rbx + rax - 1] 41 | 42 | # store and load, potentially matching 43 | AND rbx, 0b111111000000 44 | MOV qword ptr [r14 + rbx], 4096 - 64 45 | MOV qword ptr [r14 + rbx], 4096 - 128 46 | MOV rdx, qword ptr [r14] # misprediction happens here 47 | 48 | # dependent load 49 | MOV rdx, qword ptr [r14 + rdx] 50 | MFENCE 51 | -------------------------------------------------------------------------------- /src/tests/generated/priming-19-03-21.asm: -------------------------------------------------------------------------------- 1 | # This test case is an example where too large min_primer_size causes a false positive 2 | # It happens because some measurements require a fresh state of the predictors to reproduce 3 | .intel_syntax noprefix 4 | .test_case_enter: 5 | MFENCE 6 | ADD R14, 41 7 | test_case_main: 8 | .test_case_main.entry: 9 | JMP .bb0 10 | .bb0: 11 | CMOVNO RBX, RBX 12 | AND RCX, 0b111111000000 13 | ADD RCX, R14 14 | CMOVZ ECX, dword ptr [RCX] 15 | LAHF 16 | {store} ADC RAX, RAX 17 | AND RDX, 0b111111000000 18 | ADD RDX, R14 19 | SETNZ byte ptr [RDX] 20 | MOVSX EDX, BX 21 | AND RCX, 0b111111000000 22 | ADD RCX, R14 23 | CMOVNLE CX, word ptr [RCX] 24 | SUB CX, 19187 25 | JNP .bb1 26 | JMP .bb2 27 | .bb1: 28 | CMOVS AX, AX 29 | MOVZX EDX, CX 30 | {store} SBB RAX, RAX 31 | DEC AL 32 | {store} ADC RBX, RBX 33 | CMOVBE EAX, EAX 34 | {load} OR DL, DL 35 | SETB BL 36 | {disp32} JS .bb2 37 | JMP .bb3 38 | .bb2: 39 | AND RDX, 0b111111000000 40 | ADD RDX, R14 41 | ADD qword ptr [RDX], 621805592 42 | AND RAX, 0b111111000000 43 | ADD RAX, R14 44 | SUB dword ptr [RAX], 1 45 | ADD RBX, -17 46 | SUB AX, -4468 47 | {load} REX MOV DL, DL 48 | ADD EBX, -46 49 | AND RCX, 0b111111000000 50 | ADD RCX, R14 51 | MOV dword ptr [RCX], EAX 52 | AND RCX, 0b111111000000 53 | ADD RCX, R14 54 | SETP byte ptr [RCX] 55 | JMP .bb3 56 | .bb3: 57 | {store} OR RAX, RAX 58 | CMOVNZ RDX, RDX 59 | AND CL, -3 60 | AND RBX, 0b111111000000 61 | ADD RBX, R14 62 | LOCK ADC word ptr [RBX], BX 63 | REX SETNZ AL 64 | TEST RAX, -931866672 65 | ADD RAX, 289288668 66 | {store} ADD AX, AX 67 | JMP .test_case_main.exit 68 | .test_case_main.exit: 69 | .test_case_exit: 70 | SUB R14, 41 71 | MFENCE 72 | -------------------------------------------------------------------------------- /src/tests/unittests/unit_analyser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Microsoft Corporation 3 | SPDX-License-Identifier: MIT 4 | """ 5 | import unittest 6 | import sys 7 | 8 | sys.path.insert(0, '..') 9 | from analyser import EquivalenceAnalyser 10 | from interfaces import Input 11 | 12 | 13 | class AnalyserTest(unittest.TestCase): 14 | 15 | def test_subset_comparison(self): 16 | analyser = EquivalenceAnalyser() 17 | subset = [ 18 | 0b100100, 19 | 0b110101, 20 | 0b000001, 21 | ] 22 | match = analyser.check_if_all_subsets(subset) 23 | self.assertTrue(match) 24 | 25 | nonsubset = [ 26 | 0b100100, 27 | 0b101000, 28 | 0b100000, 29 | ] 30 | match = analyser.check_if_all_subsets(nonsubset) 31 | self.assertFalse(match) 32 | 33 | def test_build_eq_classes(self): 34 | analyser = EquivalenceAnalyser() 35 | dummy_input = Input() 36 | 37 | # basic collection of eq classes 38 | clss = analyser._build_equivalence_classes([dummy_input] * 4, [1, 1, 2, 2], [1, 2, 3, 4]) 39 | self.assertEqual(len(clss), 2) 40 | self.assertEqual(clss[0].ctrace, 1) 41 | self.assertEqual(clss[0].measurements[0].htrace, 1) 42 | self.assertEqual(clss[1].ctrace, 2) 43 | self.assertEqual(clss[1].measurements[0].htrace, 3) 44 | 45 | # filtering of ineffective inputs 46 | clss = analyser._build_equivalence_classes([dummy_input] * 4, [1, 2, 2, 2], [1, 2, 3, 4]) 47 | self.assertEqual(len(clss), 1) 48 | self.assertEqual(clss[0].ctrace, 2) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /src/x86/tests/unit_executor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Microsoft Corporation 3 | SPDX-License-Identifier: MIT 4 | """ 5 | import unittest 6 | import sys 7 | import os 8 | import tempfile 9 | import subprocess 10 | 11 | sys.path.insert(0, '..') 12 | from executor import X86IntelExecutor 13 | from generator import X86Generator 14 | from interfaces import TestCase, Input 15 | 16 | 17 | class ExecutorTest(unittest.TestCase): 18 | 19 | @classmethod 20 | def setUpClass(cls): 21 | cls.tc: TestCase = TestCase() 22 | asm_file = tempfile.NamedTemporaryFile(delete=False) 23 | bin_file = tempfile.NamedTemporaryFile(delete=False) 24 | 25 | with open(asm_file.name, "w") as f: 26 | f.write("movq %r14, %rax; add $512, %rax; movq (%rax), %rax\n") 27 | # f.write("nop") 28 | 29 | try: 30 | X86Generator.assemble(asm_file.name, bin_file.name) 31 | except Exception: 32 | asm_file.close() 33 | bin_file.close() 34 | os.unlink(asm_file.name) 35 | os.unlink(bin_file.name) 36 | return 37 | 38 | asm_file.close() 39 | os.unlink(asm_file.name) 40 | 41 | cls.tc.bin_path = bin_file.name 42 | cls.bin_file = bin_file 43 | 44 | @classmethod 45 | def tearDownClass(cls): 46 | cls.bin_file.close() 47 | os.unlink(cls.bin_file.name) 48 | 49 | def test_init(self): 50 | # check if the executor kernel module is properly loaded and can be initialized 51 | executor = X86IntelExecutor() 52 | self.assertTrue(executor) 53 | 54 | def test_load(self): 55 | executor = X86IntelExecutor() 56 | executor.load_test_case(self.tc) 57 | out = subprocess.check_output( 58 | "cat /sys/x86_executor/test_case > tmp.o ;" 59 | " objdump -D -b binary -m i386:x86-64 tmp.o", 60 | shell=True).decode() 61 | self.assertIn("add $0x200,%rax", out) 62 | self.assertNotIn("(bad)", out) 63 | 64 | def test_trace(self): 65 | executor = X86IntelExecutor() 66 | executor.load_test_case(self.tc) 67 | 68 | inputs = [Input(), Input()] # single zero-initialized inputs 69 | traces = executor.trace_test_case(inputs, 2) 70 | 71 | self.assertEqual(traces, [9259400833873739776, 9259400833873739776]) 72 | 73 | def test_big_batch(self): 74 | executor = X86IntelExecutor() 75 | executor.load_test_case(self.tc) 76 | 77 | inputs = [Input() for _ in range(0, 300)] 78 | traces = executor.trace_test_case(inputs, 2) 79 | 80 | self.assertEqual(traces[0], 9259400833873739776) 81 | self.assertEqual(traces[299], 9259400833873739776) 82 | 83 | 84 | if __name__ == '__main__': 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /src/tests/calls.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | MFENCE 3 | SFENCE 4 | .test_case_enter: 5 | .function_main: 6 | CALL .function_1 7 | .function_1: 8 | ADD rsp, 8 9 | CALL .function_2 10 | .function_2: 11 | ADD rsp, 8 12 | CALL .function_3 13 | .function_3: 14 | ADD rsp, 8 15 | CALL .function_4 16 | .function_4: 17 | ADD rsp, 8 18 | CALL .function_5 19 | .function_5: 20 | ADD rsp, 8 21 | CALL .function_6 22 | .function_6: 23 | ADD rsp, 8 24 | CALL .function_7 25 | .function_7: 26 | ADD rsp, 8 27 | CALL .function_8 28 | .function_8: 29 | ADD rsp, 8 30 | CALL .function_9 31 | .function_9: 32 | ADD rsp, 8 33 | CALL .function_10 34 | .function_10: 35 | ADD rsp, 8 36 | CALL .function_11 37 | .function_11: 38 | ADD rsp, 8 39 | CALL .function_12 40 | .function_12: 41 | ADD rsp, 8 42 | CALL .function_13 43 | .function_13: 44 | ADD rsp, 8 45 | CALL .function_14 46 | .function_14: 47 | ADD rsp, 8 48 | CALL .function_15 49 | .function_15: 50 | ADD rsp, 8 51 | CALL .function_16 52 | .function_16: 53 | ADD rsp, 8 54 | CALL .function_17 55 | .function_17: 56 | ADD rsp, 8 57 | CALL .function_18 58 | .function_18: 59 | ADD rsp, 8 60 | CALL .function_19 61 | .function_19: 62 | ADD rsp, 8 63 | CALL .function_20 64 | .function_20: 65 | ADD rsp, 8 66 | CALL .function_21 67 | .function_21: 68 | ADD rsp, 8 69 | CALL .function_22 70 | .function_22: 71 | ADD rsp, 8 72 | CALL .function_23 73 | .function_23: 74 | ADD rsp, 8 75 | CALL .function_24 76 | .function_24: 77 | ADD rsp, 8 78 | CALL .function_25 79 | .function_25: 80 | ADD rsp, 8 81 | CALL .function_26 82 | .function_26: 83 | ADD rsp, 8 84 | CALL .function_27 85 | .function_27: 86 | ADD rsp, 8 87 | CALL .function_28 88 | .function_28: 89 | ADD rsp, 8 90 | CALL .function_29 91 | .function_29: 92 | ADD rsp, 8 93 | CALL .function_30 94 | .function_30: 95 | ADD rsp, 8 96 | CALL .function_31 97 | .function_31: 98 | ADD rsp, 8 99 | CALL .function_32 100 | .function_32: 101 | ADD rsp, 8 102 | CALL .function_33 103 | .function_33: 104 | ADD rsp, 8 105 | CALL .function_34 106 | .function_34: 107 | ADD rsp, 8 108 | CALL .function_35 109 | .function_35: 110 | ADD rsp, 8 111 | CALL .function_36 112 | .function_36: 113 | ADD rsp, 8 114 | CALL .function_37 115 | .function_37: 116 | ADD rsp, 8 117 | CALL .function_38 118 | .function_38: 119 | ADD rsp, 8 120 | CALL .function_39 121 | .function_39: 122 | ADD rsp, 8 123 | CALL .function_40 124 | .function_40: 125 | ADD rsp, 8 126 | CALL .function_41 127 | .function_41: 128 | ADD rsp, 8 129 | CALL .function_42 130 | .function_42: 131 | ADD rsp, 8 132 | CALL .function_43 133 | .function_43: 134 | ADD rsp, 8 135 | CALL .function_44 136 | .function_44: 137 | ADD rsp, 8 138 | CALL .function_45 139 | .function_45: 140 | ADD rsp, 8 141 | CALL .function_46 142 | .function_46: 143 | ADD rsp, 8 144 | CALL .function_47 145 | .function_47: 146 | ADD rsp, 8 147 | CALL .function_48 148 | .function_48: 149 | ADD rsp, 8 150 | CALL .function_49 151 | .function_49: 152 | ADD rsp, 8 153 | CALL .function_50 154 | .function_50: 155 | ADD rsp, 8 156 | .test_case_exit: 157 | MFENCE 158 | -------------------------------------------------------------------------------- /src/x86/executor/main.h: -------------------------------------------------------------------------------- 1 | /// File: Main Header 2 | /// 3 | // Copyright (C) Microsoft Corporation 4 | // SPDX-License-Identifier: MIT 5 | 6 | #ifndef X86_EXECUTOR 7 | #define X86_EXECUTOR 8 | 9 | #include 10 | 11 | #define DEBUG 0 12 | 13 | // Executor Configuration Interface 14 | extern long uarch_reset_rounds; 15 | #define UARCH_RESET_ROUNDS_DEFAULT 1 16 | extern uint64_t ssbp_patch_control; 17 | #define SSBP_PATH_DEFAULT 0b011 18 | extern char enable_faulty_page; 19 | #define ENABLE_FAULTY_DEFAULT 0 20 | extern char pre_run_flush; 21 | #define PRE_RUN_FLUSH_DEFAULT 1 22 | extern char *attack_template; 23 | 24 | // Attack configuration 25 | #ifndef L1D_ASSOCIATIVITY 26 | #error "Undefined associativity" 27 | #elif L1D_ASSOCIATIVITY != 12 && L1D_ASSOCIATIVITY != 8 28 | #warning "Unsupported/corrupted L1D associativity. Falling back to 8-way" 29 | #define L1D_ASSOCIATIVITY 8 30 | #endif 31 | 32 | // Measurement results 33 | #define HTRACE_WIDTH 1 34 | #define NUM_PFC 3 35 | 36 | typedef struct Measurement 37 | { 38 | uint64_t htrace[HTRACE_WIDTH]; 39 | uint64_t pfc[NUM_PFC]; 40 | } measurement_t; 41 | 42 | extern measurement_t *measurements; 43 | 44 | // Sandbox 45 | #define WORKING_MEMORY_SIZE 1048576 // 256KB 46 | #define MAIN_REGION_SIZE 4096 47 | #define FAULTY_REGION_SIZE 4096 48 | #define OVERFLOW_REGION_SIZE 4096 49 | #define REG_INITIALIZATION_REGION_SIZE 64 50 | #define EVICT_REGION_SIZE (L1D_ASSOCIATIVITY * 4096) 51 | 52 | typedef struct Sandbox 53 | { 54 | char eviction_region[EVICT_REGION_SIZE]; // region used in Prime+Probe for priming 55 | char lower_overflow[OVERFLOW_REGION_SIZE]; // zero-initialized region for accidental overflows 56 | char main_region[MAIN_REGION_SIZE]; // first input page. does not cause faults 57 | char faulty_region[FAULTY_REGION_SIZE]; // second input. causes a (configurable) fault 58 | char upper_overflow[OVERFLOW_REGION_SIZE]; // zero-initialized region for accidental overflows 59 | uint64_t stored_rsp; 60 | measurement_t latest_measurement; // measurement results 61 | } sandbox_t; 62 | 63 | extern sandbox_t *sandbox; 64 | extern void *stack_base; 65 | 66 | #define REG_INIT_OFFSET 8192 // (MAIN_REGION_SIZE + FAULTY_REGION_SIZE) 67 | #define EVICT_REGION_OFFSET (EVICT_REGION_SIZE + OVERFLOW_REGION_SIZE) 68 | #define RSP_OFFSET 12288 // (MAIN_REGION_SIZE + FAULTY_REGION_SIZE + OVERFLOW_REGION_SIZE) 69 | #define MEASUREMENT_OFFSET 12296 // RSP_OFFSET + sizeof(stored_rsp) 70 | 71 | // Test Case 72 | extern char *test_case; 73 | #define MAX_TEST_CASE_SIZE 4096 // must be exactly 1 page to detect sysfs buffering 74 | extern char *measurement_code; 75 | #define MAX_MEASUREMENT_CODE_SIZE (4096 * 2) 76 | extern char *measurement_template; 77 | 78 | // Inputs 79 | #define REG_INITIALIZATION_REGION_SIZE_ALIGNED 4096 80 | #define INPUT_SIZE (MAIN_REGION_SIZE + FAULTY_REGION_SIZE + REG_INITIALIZATION_REGION_SIZE_ALIGNED) 81 | extern uint64_t *inputs; 82 | extern volatile size_t n_inputs; 83 | 84 | // Shared functions 85 | int trace_test_case(void); 86 | int load_template(size_t tc_size); 87 | void template_l1d_prime_probe(void); 88 | void template_l1d_flush_reload(void); 89 | void template_l1d_evict_reload(void); 90 | 91 | #endif -------------------------------------------------------------------------------- /src/tests/unittests/unit_isa_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Microsoft Corporation 3 | SPDX-License-Identifier: MIT 4 | """ 5 | import unittest 6 | 7 | import sys 8 | import os 9 | import tempfile 10 | 11 | sys.path.insert(0, '..') 12 | from isa_loader import InstructionSet 13 | from interfaces import OT, InstructionSpec 14 | 15 | basic = """ 16 | [ 17 | {"name": "TEST", "category": "CATEGORY", "control_flow": true, 18 | "operands": [ 19 | {"type_": "MEM", "values": [], "src": true, "dest": true, "width": 16}, 20 | {"type_": "REG", "values": ["AX"], "src": true, "dest": false, "width": 16} 21 | ], 22 | "implicit_operands": [ 23 | {"type_": "FLAGS", "values": ["w", "r", "undef", "w", "w", "", "", "", "w"], "src": false, "dest": false, "width": 0} 24 | ] 25 | } 26 | ] 27 | """ 28 | 29 | duplicate = """ 30 | [ 31 | {"name": "TEST", "category": "CATEGORY", "control_flow": false, 32 | "operands": [ 33 | {"type_": "MEM", "values": [], "src": true, "dest": true, "width": 16} 34 | ], 35 | "implicit_operands": [] 36 | }, 37 | {"name": "TEST", "category": "CATEGORY", "control_flow": false, 38 | "operands": [ 39 | {"type_": "MEM", "values": [], "src": true, "dest": true, "width": 16} 40 | ], 41 | "implicit_operands": [] 42 | } 43 | ] 44 | """ 45 | 46 | 47 | class InstructionSetParserTest(unittest.TestCase): 48 | 49 | def test_parsing(self): 50 | spec_file = tempfile.NamedTemporaryFile("w", delete=False) 51 | with open(spec_file.name, "w") as f: 52 | f.write(basic) 53 | 54 | instruction_set = InstructionSet(spec_file.name) 55 | spec_file.close() 56 | os.unlink(spec_file.name) 57 | 58 | spec: InstructionSpec = instruction_set.instructions[0] 59 | self.assertEqual(spec.name, "TEST") 60 | self.assertEqual(spec.category, "CATEGORY") 61 | self.assertEqual(spec.has_mem_operand, True) 62 | self.assertEqual(spec.has_write, True) 63 | self.assertEqual(spec.control_flow, True) 64 | 65 | self.assertEqual(len(spec.operands), 2) 66 | op1 = spec.operands[0] 67 | self.assertEqual(op1.type, OT.MEM) 68 | self.assertEqual(op1.width, 16) 69 | self.assertEqual(op1.src, True) 70 | self.assertEqual(op1.dest, True) 71 | 72 | op2 = spec.operands[1] 73 | self.assertEqual(op2.type, OT.REG) 74 | self.assertEqual(op2.values, ["AX"]) 75 | self.assertEqual(op2.src, True) 76 | self.assertEqual(op2.dest, False) 77 | 78 | self.assertEqual(len(spec.implicit_operands), 1) 79 | flags = spec.implicit_operands[0] 80 | self.assertEqual(flags.type, OT.FLAGS) 81 | self.assertEqual(flags.values, ['w', 'r', 'undef', 'w', 'w', '', '', '', 'w']) 82 | 83 | def test_dedup_identical(self): 84 | spec_file = tempfile.NamedTemporaryFile("w", delete=False) 85 | with open(spec_file.name, "w") as f: 86 | f.write(duplicate) 87 | 88 | instruction_set = InstructionSet(spec_file.name) 89 | spec_file.close() 90 | os.unlink(spec_file.name) 91 | 92 | self.assertEqual(len(instruction_set.instructions), 1, "No deduplication") 93 | 94 | 95 | if __name__ == '__main__': 96 | unittest.main() 97 | -------------------------------------------------------------------------------- /src/input_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Input Generation 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | import random 8 | from typing import List, Tuple 9 | from interfaces import Input, InputTaint, InputGenerator 10 | from config import CONF, ConfigException 11 | from service import LOGGER 12 | 13 | POW32 = pow(2, 32) 14 | 15 | 16 | class RandomInputGenerator(InputGenerator): 17 | """ Simple 32-bit LCG with a=2891336453 and c=54321 """ 18 | 19 | def __init__(self): 20 | super().__init__() 21 | self.input_mask = pow(2, (CONF.input_gen_entropy_bits % 33)) - 1 22 | 23 | def generate(self, seed: int, count: int) -> List[Input]: 24 | if seed == 0: 25 | seed = random.randint(0, pow(2, 32) - 1) 26 | LOGGER.info("input_gen", str(seed)) 27 | 28 | generated_inputs = [] 29 | for i in range(count): 30 | input_, seed = self._generate_one(seed) 31 | generated_inputs.append(input_) 32 | return generated_inputs 33 | 34 | def extend_equivalence_classes(self, 35 | inputs: List[Input], 36 | taints: List[InputTaint]) -> List[Input]: 37 | if len(inputs) != len(taints): 38 | raise Exception("Error: Cannot extend inputs. " 39 | "The number of taints does not match the number of inputs.") 40 | 41 | # continue the sequence of random values from the last one 42 | # in the previous input sequence 43 | _, seed = self._generate_one(inputs[-1].seed) 44 | 45 | # produce a new sequence of random inputs, but copy the tainted values from 46 | # the previous sequence 47 | new_inputs = [] 48 | for i, input_ in enumerate(inputs): 49 | taint = taints[i] 50 | new_input, seed = self._generate_one(seed) 51 | for j in range(input_.data_size): 52 | if taint[j]: 53 | new_input[j] = input_[j] 54 | new_inputs.append(new_input) 55 | 56 | return new_inputs 57 | 58 | def _generate_one(self, seed: int) -> Tuple[Input, int]: 59 | input_ = Input() 60 | input_.seed = seed 61 | 62 | randint = seed 63 | for i in range(input_.data_size): 64 | # this weird implementation is a legacy of our old PRNG. 65 | # basically, it's a 32-bit PRNG, assigned to 4-byte chucks of memory 66 | # TODO: replace it with a more sane implementation after the artifact is done 67 | randint = ((randint * 2891336453) % POW32 + 54321) % POW32 68 | masked_rvalue = (randint ^ (randint >> 16)) & self.input_mask 69 | masked_rvalue = masked_rvalue << 6 70 | input_[i] = masked_rvalue << 32 71 | 72 | randint = ((randint * 2891336453) % POW32 + 54321) % POW32 73 | masked_rvalue = (randint ^ (randint >> 16)) & self.input_mask 74 | masked_rvalue = masked_rvalue << 6 75 | input_[i] += masked_rvalue 76 | 77 | # again, to emulate the legacy (and kinda broken) input generator, 78 | # initialize only the first 32 bits of registers 79 | for i in range(CONF.input_register_region_size // 8): 80 | input_[-i - 1] = input_[-i - 1] % POW32 81 | 82 | return input_, randint 83 | 84 | 85 | def get_input_generator() -> InputGenerator: 86 | options = { 87 | 'random': RandomInputGenerator, 88 | } 89 | if CONF.input_generator not in options: 90 | ConfigException("unknown input_generator in config.py") 91 | exit(1) 92 | return options[CONF.input_generator]() 93 | -------------------------------------------------------------------------------- /src/tests/rollback_fence_and_expire.asm: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .test_case_enter: 3 | 4 | MOV rax, 0 5 | 6 | AND rbx, 0b1 # reduce the entropy in rbx 7 | CMP rbx, 0 8 | JE .l1 9 | .l0: 10 | # rbx != 0 11 | LFENCE 12 | NOP 13 | MOV rax, 128 14 | JMP .fin 15 | .l1: 16 | # >250 instructions 17 | NOP 18 | NOP 19 | NOP 20 | NOP 21 | NOP 22 | NOP 23 | NOP 24 | NOP 25 | NOP 26 | NOP 27 | NOP 28 | NOP 29 | NOP 30 | NOP 31 | NOP 32 | NOP 33 | NOP 34 | NOP 35 | NOP 36 | NOP 37 | NOP 38 | NOP 39 | NOP 40 | NOP 41 | NOP 42 | NOP 43 | NOP 44 | NOP 45 | NOP 46 | NOP 47 | NOP 48 | NOP 49 | NOP 50 | NOP 51 | NOP 52 | NOP 53 | NOP 54 | NOP 55 | NOP 56 | NOP 57 | NOP 58 | NOP 59 | NOP 60 | NOP 61 | NOP 62 | NOP 63 | NOP 64 | NOP 65 | NOP 66 | NOP 67 | NOP 68 | NOP 69 | NOP 70 | NOP 71 | NOP 72 | NOP 73 | NOP 74 | NOP 75 | NOP 76 | NOP 77 | NOP 78 | NOP 79 | NOP 80 | NOP 81 | NOP 82 | NOP 83 | NOP 84 | NOP 85 | NOP 86 | NOP 87 | NOP 88 | NOP 89 | NOP 90 | NOP 91 | NOP 92 | NOP 93 | NOP 94 | NOP 95 | NOP 96 | NOP 97 | NOP 98 | NOP 99 | NOP 100 | NOP 101 | NOP 102 | NOP 103 | NOP 104 | NOP 105 | NOP 106 | NOP 107 | NOP 108 | NOP 109 | NOP 110 | NOP 111 | NOP 112 | NOP 113 | NOP 114 | NOP 115 | NOP 116 | NOP 117 | NOP 118 | NOP 119 | NOP 120 | NOP 121 | NOP 122 | NOP 123 | NOP 124 | NOP 125 | NOP 126 | NOP 127 | NOP 128 | NOP 129 | NOP 130 | NOP 131 | NOP 132 | NOP 133 | NOP 134 | NOP 135 | NOP 136 | NOP 137 | NOP 138 | NOP 139 | NOP 140 | NOP 141 | NOP 142 | NOP 143 | NOP 144 | NOP 145 | NOP 146 | NOP 147 | NOP 148 | NOP 149 | NOP 150 | NOP 151 | NOP 152 | NOP 153 | NOP 154 | NOP 155 | NOP 156 | NOP 157 | NOP 158 | NOP 159 | NOP 160 | NOP 161 | NOP 162 | NOP 163 | NOP 164 | NOP 165 | NOP 166 | NOP 167 | NOP 168 | NOP 169 | NOP 170 | NOP 171 | NOP 172 | NOP 173 | NOP 174 | NOP 175 | NOP 176 | NOP 177 | NOP 178 | NOP 179 | NOP 180 | NOP 181 | NOP 182 | NOP 183 | NOP 184 | NOP 185 | NOP 186 | NOP 187 | NOP 188 | NOP 189 | NOP 190 | NOP 191 | NOP 192 | NOP 193 | NOP 194 | NOP 195 | NOP 196 | NOP 197 | NOP 198 | NOP 199 | NOP 200 | NOP 201 | NOP 202 | NOP 203 | NOP 204 | NOP 205 | NOP 206 | NOP 207 | NOP 208 | NOP 209 | NOP 210 | NOP 211 | NOP 212 | NOP 213 | NOP 214 | NOP 215 | NOP 216 | NOP 217 | NOP 218 | NOP 219 | NOP 220 | NOP 221 | NOP 222 | NOP 223 | NOP 224 | NOP 225 | NOP 226 | NOP 227 | NOP 228 | NOP 229 | NOP 230 | NOP 231 | NOP 232 | NOP 233 | NOP 234 | NOP 235 | NOP 236 | NOP 237 | NOP 238 | NOP 239 | NOP 240 | NOP 241 | NOP 242 | NOP 243 | NOP 244 | NOP 245 | NOP 246 | NOP 247 | NOP 248 | NOP 249 | NOP 250 | NOP 251 | NOP 252 | NOP 253 | NOP 254 | NOP 255 | NOP 256 | NOP 257 | NOP 258 | NOP 259 | NOP 260 | NOP 261 | NOP 262 | NOP 263 | NOP 264 | NOP 265 | NOP 266 | NOP 267 | NOP 268 | NOP 269 | NOP 270 | NOP 271 | NOP 272 | NOP 273 | NOP 274 | NOP 275 | NOP 276 | NOP 277 | NOP 278 | NOP 279 | NOP 280 | MOV rax, 256 281 | 282 | .fin: 283 | MOV rax, qword ptr [r14 + rax] 284 | MFENCE -------------------------------------------------------------------------------- /src/tests/unittests/unit_generators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Microsoft Corporation 3 | SPDX-License-Identifier: MIT 4 | """ 5 | import unittest 6 | import tempfile 7 | import sys 8 | import subprocess 9 | import os 10 | 11 | sys.path.insert(0, '..') 12 | from generator import X86RandomGenerator, X86Printer, X86PatchUndefinedFlagsPass 13 | from isa_loader import InstructionSet 14 | from interfaces import TestCase, Function 15 | from config import CONF 16 | 17 | 18 | class X86RandomGeneratorTest(unittest.TestCase): 19 | 20 | def test_x86_all_instructions(self): 21 | instruction_set = InstructionSet('unittests/min_x86.json', 22 | CONF.supported_categories) 23 | generator = X86RandomGenerator(instruction_set) 24 | func = generator.generate_function("function_main") 25 | printer = X86Printer() 26 | all_instructions = ['.intel_syntax noprefix\n'] 27 | 28 | # try generating instruction strings 29 | for bb in func: 30 | for instruction_spec in generator.non_control_flow_instructions: 31 | # fill up with random operand, following the spec 32 | inst = generator.generate_instruction(instruction_spec) 33 | bb.insert_after(bb.get_last(), inst) 34 | 35 | for instr in bb: 36 | instr_str = printer.instruction_to_str(instr) 37 | self.assertTrue(instr_str, f'Instruction {instr} was not generated.') 38 | all_instructions.append(instr_str + "\n") 39 | 40 | asm_file = tempfile.NamedTemporaryFile("w", delete=False) 41 | bin_file = tempfile.NamedTemporaryFile("w", delete=False) 42 | for i in all_instructions: 43 | asm_file.write(i) 44 | 45 | # check if the generated instructions are valid 46 | assembly_failed = False 47 | try: 48 | generator.assemble(asm_file.name, bin_file.name) 49 | except subprocess.CalledProcessError: 50 | assembly_failed = True 51 | else: 52 | bin_file.close() 53 | os.unlink(bin_file.name) 54 | asm_file.close() 55 | os.unlink(asm_file.name) 56 | 57 | if assembly_failed: 58 | self.fail("Generated invalid instruction(s)") 59 | 60 | def test_x86_asm_parsing_basic(self): 61 | CONF.gpr_blocklist = [] 62 | CONF.instruction_blocklist = [] 63 | 64 | instruction_set = InstructionSet('unittests/min_x86.json') 65 | generator = X86RandomGenerator(instruction_set) 66 | tc: TestCase = generator.parse_existing_test_case("unittests/asm_basic.asm") 67 | self.assertEqual(len(tc.functions), 1) 68 | 69 | main = tc.functions[0] 70 | self.assertEqual(main.name, ".function_main") 71 | self.assertEqual(len(main), 4) 72 | main_iter = iter(main) 73 | 74 | entry = next(main_iter) 75 | bb0 = next(main_iter) 76 | bb1 = next(main_iter) 77 | exit_ = next(main_iter) 78 | 79 | self.assertEqual(entry.successors[0], bb0) 80 | self.assertEqual(bb0.successors[0], bb1) 81 | self.assertEqual(bb1.successors[0], exit_) 82 | 83 | def test_x86_undef_flag_patch(self): 84 | instruction_set = InstructionSet('unittests/min_x86.json') 85 | undef_instr_spec = list(filter(lambda x: x.name == 'BSF', instruction_set.instructions))[0] 86 | read_instr_spec = list(filter(lambda x: x.name == 'LAHF', instruction_set.instructions))[0] 87 | 88 | generator = X86RandomGenerator(instruction_set) 89 | undef_instr = generator.generate_instruction(undef_instr_spec) 90 | read_instr = generator.generate_instruction(read_instr_spec) 91 | 92 | test_case = TestCase() 93 | test_case.functions = [Function(".function_main")] 94 | bb = test_case.functions[0].entry 95 | bb.insert_after(bb.get_last(), undef_instr) 96 | bb.insert_after(bb.get_last(), read_instr) 97 | 98 | X86PatchUndefinedFlagsPass(instruction_set, generator).run_on_test_case(test_case) 99 | self.assertEqual(len(bb), 3) 100 | 101 | 102 | if __name__ == '__main__': 103 | unittest.main() 104 | -------------------------------------------------------------------------------- /src/tests/evaluation/impact_of_configuration/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | SCRIPT=$(realpath $0) 5 | SCRIPT_DIR=$(dirname $SCRIPT) 6 | MAX_ROUNDS=10000 7 | 8 | timestamp=$(date '+%y-%m-%d-%H-%M') 9 | revizor_src='../../../' 10 | instructions="$revizor_src/x86/isa_spec/base.json" 11 | 12 | # RESULTS_DIR="results" 13 | exp_dir="$RESULTS_DIR/$timestamp" 14 | mkdir $exp_dir 15 | 16 | log="$exp_dir/experiment.log" 17 | touch $log 18 | result="$exp_dir/aggregated.txt" 19 | touch $result 20 | 21 | # Defaults 22 | MEM_IN_PAIRS="false" 23 | AVOID_DATA_DEP="false" 24 | INPUT_ENTROPY=4 25 | INPUTS_PER_CLS=2 26 | BB_PER_FUNCTION=2 27 | TC_SIZE=24 28 | NUM_MEM=12 29 | 30 | # Experiment Configuration 31 | REPS=9 32 | 33 | 34 | function time_to_violation() { 35 | local conf=$1 36 | local name=$2 37 | 38 | ${revizor_src}/cli.py fuzz -s $instructions -c $conf -n $MAX_ROUNDS -i 50 -w $exp_dir > "$exp_dir/tmp.txt" 39 | cat "$exp_dir/tmp.txt" >> $log 40 | cat "$exp_dir/tmp.txt" | awk '/Test Cases:/{tc=$3} /Duration:/{dur=$2} /Finished/{printf "%s, %d, %d\n", name, tc, dur}' name=$name >> $result 41 | # cat tmp.txt | awk '/Test Cases:/{tc=$3} /Patterns:/{p=$2} /Fully covered:/{fc=$3} /Longest uncovered:/{lu=$3} /Duration:/{dur=$2} /Finished/{printf "%s, %d, %d, %d, %d, %d\n", name, tc, p, fc, lu, dur}' name=$name >> $result 42 | 43 | } 44 | 45 | function measure_detection_times() { 46 | printf "" > $result 47 | for name in v1 v4 mds ; do 48 | echo " - Running $name" | tee -a "$log" 49 | template="$SCRIPT_DIR/$name.yaml" 50 | conf="$SCRIPT_DIR/conf.yaml" 51 | 52 | cp $template $conf 53 | echo "input_gen_entropy_bits: $INPUT_ENTROPY 54 | min_bb_per_function: $BB_PER_FUNCTION 55 | max_bb_per_function: $BB_PER_FUNCTION 56 | test_case_size: $TC_SIZE 57 | avg_mem_accesses: $NUM_MEM 58 | avoid_data_dependencies: $AVOID_DATA_DEP 59 | generate_memory_accesses_in_pairs: $MEM_IN_PAIRS 60 | inputs_per_class: $INPUTS_PER_CLS" >> $conf 61 | 62 | for i in $(seq 1 $REPS); do 63 | time_to_violation $conf "$name,$i" 64 | tail -n1 $result 65 | done 66 | done 67 | echo "" 68 | echo "Summary" 69 | echo "Name, Mean, Standard Deviation" 70 | datamash -t, groupby 1 mean 3 sstdev 3 < $result 71 | } 72 | 73 | echo "================================================================" 74 | echo "Baseline" 75 | measure_detection_times 76 | 77 | echo "================================================================" 78 | echo "Memory in pairs" 79 | MEM_IN_PAIRS="true" 80 | measure_detection_times 81 | MEM_IN_PAIRS="false" 82 | 83 | echo "================================================================" 84 | echo "Avoid data dependencies" 85 | AVOID_DATA_DEP="true" 86 | measure_detection_times 87 | AVOID_DATA_DEP="false" 88 | 89 | echo "================================================================" 90 | echo "Input Entropy" 91 | old_val=$INPUT_ENTROPY 92 | for INPUT_ENTROPY in $(seq 2 2 8); do 93 | echo "- $INPUT_ENTROPY" 94 | measure_detection_times 95 | done 96 | INPUT_ENTROPY=$old_val 97 | 98 | echo "================================================================" 99 | echo "Inputs per Eq Class" 100 | old_val=$INPUTS_PER_CLS 101 | for INPUTS_PER_CLS in $(seq 4 2 8); do 102 | echo "- $INPUTS_PER_CLS" 103 | measure_detection_times 104 | done 105 | INPUTS_PER_CLS=$old_val 106 | 107 | echo "================================================================" 108 | echo "BB per function" 109 | old_val=$BB_PER_FUNCTION 110 | for BB_PER_FUNCTION in $(seq 2 1 5); do 111 | echo "- $BB_PER_FUNCTION" 112 | measure_detection_times 113 | done 114 | BB_PER_FUNCTION=$old_val 115 | 116 | echo "================================================================" 117 | echo "Test Case Size" 118 | old_val=$TC_SIZE 119 | for TC_SIZE in $(seq 16 8 48); do 120 | echo "- $TC_SIZE" 121 | measure_detection_times 122 | done 123 | TC_SIZE=$old_val 124 | 125 | echo "================================================================" 126 | echo "Number of mem. acceses per test case" 127 | old_val=$NUM_MEM 128 | for NUM_MEM in $(seq 4 4 20); do 129 | echo "- $NUM_MEM" 130 | measure_detection_times 131 | done 132 | NUM_MEM=$old_val -------------------------------------------------------------------------------- /src/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | File: Command Line Interface 4 | 5 | Copyright (C) Microsoft Corporation 6 | SPDX-License-Identifier: MIT 7 | """ 8 | 9 | import os 10 | import yaml 11 | from typing import Dict 12 | from argparse import ArgumentParser 13 | from fuzzer import Fuzzer 14 | from postprocessor import Postprocessor 15 | from config import CONF 16 | from service import LOGGER 17 | 18 | 19 | def main(): 20 | parser = ArgumentParser(description='', add_help=False) 21 | subparsers = parser.add_subparsers(dest='subparser_name') 22 | 23 | # Fuzzing 24 | parser_fuzz = subparsers.add_parser('fuzz') 25 | parser_fuzz.add_argument( 26 | "-s", "--instruction-set", 27 | type=str, 28 | required=True 29 | ) 30 | parser_fuzz.add_argument( 31 | "-c", "--config", 32 | type=str, 33 | required=False 34 | ) 35 | parser_fuzz.add_argument( 36 | "-n", "--num-test-cases", 37 | type=int, 38 | default=1, 39 | help="Number of test cases.", 40 | ) 41 | parser_fuzz.add_argument( 42 | "-i", "--num-inputs", 43 | type=int, 44 | default=100, 45 | help="Number of inputs per test case.", 46 | ) 47 | parser_fuzz.add_argument( 48 | '-w', '--working-directory', 49 | type=str, 50 | default='', 51 | ) 52 | parser_fuzz.add_argument( 53 | '-t', '--testcase', 54 | type=str, 55 | default=None, 56 | help="Use an existing test case" 57 | ) 58 | parser_fuzz.add_argument( 59 | '--timeout', 60 | type=int, 61 | default=0, 62 | help="Run fuzzing with a time limit [seconds]. No timeout when set to zero." 63 | ) 64 | parser_fuzz.add_argument( 65 | '--nonstop', 66 | action='store_true', 67 | help="Don't stop after detecting an unexpected result" 68 | ) 69 | 70 | parser_mini = subparsers.add_parser('minimize') 71 | parser_mini.add_argument( 72 | '--infile', '-i', 73 | type=str, 74 | required=True, 75 | ) 76 | parser_mini.add_argument( 77 | '--outfile', '-o', 78 | type=str, 79 | required=True, 80 | ) 81 | parser_mini.add_argument( 82 | "-c", "--config", 83 | type=str, 84 | required=False 85 | ) 86 | parser_mini.add_argument( 87 | "-n", "--num-inputs", 88 | type=int, 89 | default=100, 90 | help="Number of inputs per test case.", 91 | ) 92 | parser_mini.add_argument( 93 | "-f", "--add-fences", 94 | action='store_true', 95 | default=False, 96 | help="Add as many LFENCEs as possible, while preserving the violation.", 97 | ) 98 | parser_mini.add_argument( 99 | "-s", "--instruction-set", 100 | type=str, 101 | required=True 102 | ) 103 | 104 | args = parser.parse_args() 105 | 106 | # Update configuration 107 | if args.config: 108 | CONF.config_path = args.config 109 | with open(args.config, "r") as f: 110 | config_update: Dict = yaml.safe_load(f) 111 | for var, value in config_update.items(): 112 | CONF.set(var, value) 113 | CONF.sanity_check() 114 | LOGGER.set_logging_modes() 115 | 116 | # Fuzzing 117 | if args.subparser_name == 'fuzz': 118 | # Make sure we're ready for fuzzing 119 | if args.working_directory and not os.path.isdir(args.working_directory): 120 | SystemExit("The working directory does not exist") 121 | 122 | # Normal fuzzing mode 123 | fuzzer = Fuzzer(args.instruction_set, args.working_directory, args.testcase) 124 | fuzzer.start( 125 | args.num_test_cases, 126 | args.num_inputs, 127 | args.timeout, 128 | args.nonstop, 129 | ) 130 | return 131 | 132 | # Test Case minimisation 133 | if args.subparser_name == "minimize": 134 | CONF.coverage_type = 'none' 135 | postprocessor = Postprocessor(args.instruction_set) 136 | postprocessor.minimize(args.infile, args.outfile, args.num_inputs, args.add_fences) 137 | return 138 | 139 | raise Exception("Unreachable") 140 | 141 | 142 | if __name__ == '__main__': 143 | main() 144 | -------------------------------------------------------------------------------- /src/analyser.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: various ways to compare ctraces with htraces 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | from collections import defaultdict 8 | from typing import List, Dict 9 | 10 | from interfaces import HTrace, CTrace, Input, EquivalenceClass, Analyser, Measurement 11 | from config import CONF 12 | from service import STAT, LOGGER, TWOS_COMPLEMENT_MASK_64, bit_count 13 | 14 | 15 | class EquivalenceAnalyser(Analyser): 16 | """ 17 | TODO: the description is outdated 18 | The main analysis function. 19 | 20 | Checks if all inputs that agree on their contract traces (ctraces) also agree 21 | on the hardware traces (htraces). To this end, we use relational theory 22 | [see https://en.wikipedia.org/wiki/Equivalence_class] 23 | 24 | From the theory perspective, the fuzzing results establish a relation between the ctraces 25 | and the htraces. E.g., if an input produced a ctrace C and an htrace H, then C is 26 | related to H. Because of the retries, though, we may have several htraces per input. 27 | Therefore, the actual relation is C->set(H). 28 | 29 | Based on this relations, we establish equivalence classes for all ctraces. 30 | This function checks if all equivalence classes have only one entry. 31 | 32 | :return A list of input IDs where ctraces disagree with htraces and a list of inputs that 33 | require retries 34 | """ 35 | 36 | def filter_violations(self, 37 | inputs: List[Input], 38 | ctraces: List[CTrace], 39 | htraces: List[HTrace], 40 | stats=False) -> List[EquivalenceClass]: 41 | 42 | equivalence_classes: List[EquivalenceClass] = self._build_equivalence_classes( 43 | inputs, ctraces, htraces, stats) 44 | self.coverage.analyser_hook(equivalence_classes) 45 | 46 | violations: List[EquivalenceClass] = [] 47 | for eq_cls in equivalence_classes: 48 | # if all htraces in the class match, it's definitely not a violation 49 | if len(eq_cls.htrace_map) < 2: 50 | continue 51 | 52 | if not CONF.analyser_permit_subsets: 53 | violations.append(eq_cls) 54 | continue 55 | 56 | htraces = list(eq_cls.htrace_map.keys()) 57 | if not self.check_if_all_subsets(htraces): 58 | violations.append(eq_cls) 59 | 60 | return violations 61 | 62 | @staticmethod 63 | def check_if_all_subsets(htraces: List[HTrace]) -> bool: 64 | max_htrace = max(htraces, key=bit_count) 65 | mask = max_htrace ^ TWOS_COMPLEMENT_MASK_64 66 | for htrace in htraces: 67 | masked_trace = htrace & mask 68 | if masked_trace != 0: 69 | return False 70 | 71 | return True 72 | 73 | def _build_equivalence_classes(self, 74 | inputs: List[Input], 75 | ctraces: List[CTrace], 76 | htraces: List[HTrace], 77 | stats=False) -> List[EquivalenceClass]: 78 | """ 79 | Collect inputs into equivalence classes based on ctraces and group the inputs within 80 | the equivalence class by the htrace 81 | """ 82 | 83 | # build eq. classes 84 | eq_class_map: Dict[CTrace, EquivalenceClass] = defaultdict(lambda: EquivalenceClass()) 85 | for i, ctrace in enumerate(ctraces): 86 | eq_cls = eq_class_map[ctrace] 87 | eq_cls.ctrace = ctrace 88 | eq_cls.measurements.append(Measurement(i, inputs[i], ctrace, htraces[i])) 89 | 90 | # fine effective classes 91 | effective_classes: List[EquivalenceClass] = [] 92 | for eq_cls in eq_class_map.values(): 93 | if len(eq_cls.measurements) > 1: 94 | effective_classes.append(eq_cls) 95 | effective_classes.sort(key=lambda x: x.ctrace) 96 | 97 | if stats: 98 | STAT.eff_classes += len(effective_classes) 99 | STAT.single_entry_classes += len(eq_class_map) - len(effective_classes) 100 | 101 | # build maps of htraces 102 | for eq_cls in effective_classes: 103 | eq_cls.build_htrace_map() 104 | 105 | return effective_classes 106 | 107 | 108 | def get_analyser() -> Analyser: 109 | options = { 110 | 'equivalence-classes': EquivalenceAnalyser, 111 | } 112 | if CONF.analyser not in options: 113 | LOGGER.error("unknown analyser in the config file") 114 | exit(1) 115 | return options[CONF.analyser]() 116 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration File 2 | 3 | Below is a list of the available configuration options for Revizor, which are passed down to Revizor via a config file. 4 | For an example of how to write the config file, see [src/tests/big-fuzz.yaml](src/tests/big-fuzz.yaml). 5 | 6 | Some of the options are omitted from the list as they should not be used in a normal fuzzing campaign. 7 | For a complete list, see `src/config.py`. 8 | 9 | ## Contract 10 | 11 | * `contract_execution_clause` : List[str]: Execution clause. 12 | Available options: "seq", "cond", "bpas", "null-injection". 13 | * `contract_observation_clause` [str]: Observation clause. 14 | Available options: "l1d", "memory", "ct", "pc", "ct-nonspecstore", "ctr", "arch". 15 | 16 | ## General Configuration 17 | 18 | * `no_priming` [bool]: Disable priming. 19 | * `logging_modes` List[str]: Verbosity of the output. 20 | Available options: 21 | `info` - general information about the progress of fuzzing; 22 | `stat` - statistics the end of the fuzzing campaign; 23 | `fuzzer_debug` - detailed information about the fuzzing progress and about the detected vulnerabilities; 24 | `fuzzer_trace` - very detailed information about the fuzzing progress (not recommended); 25 | `model_debug` - detailed information about the execution of each test case on the model; 26 | `coverage_debug` - detailed information about the changes in coverage. 27 | * `multiline_output` [bool]: Print each output message on a separate line. 28 | Preferred when piping the log into a file. 29 | 30 | # Model Configuration 31 | 32 | * `model` [str]: Model type. 33 | Only one option is currently supported - "x86-unicorn" (default). 34 | * `model_max_nesting` [int]: Maximum number of simulated mispredictions. 35 | * `model_max_spec_window` [int]: Size of the speculation window. 36 | 37 | ## Generator Configuration 38 | 39 | * `instruction_set` [str]: Tested ISA. 40 | Only one option is currently supported - "x86-64" (default). 41 | * `generator` [str]: Test case generator type. 42 | Only one option is currently supported - "random" (default). 43 | * `test_case_generator_seed` [int]: Test case generation seed. 44 | Will use a random seed if set to zero. 45 | * `min_bb_per_function` [int]: Minimum number of basic blocks per test case. 46 | * `max_bb_per_function` [int]: Maximum number of basic blocks per test case. 47 | * `test_case_size` [int]: Number of instructions per test case. 48 | The actual size might be larger because of the instrumentation. 49 | * `avg_mem_accesses` [int]: Average number of memory accesses per test case. 50 | * `supported_categories` [list(str)]: List of instruction categories to be used when generating a test case. 51 | Used to filter out instructions from the instruction set file passed via command line (`--instruction-set`). 52 | * `extended_instruction_blocklist` [list(str)]: List of instructions to be excluded by the generator. 53 | Used to filter out instructions from the instruction set file passed via command line (`--instruction-set`). 54 | 55 | # Input Generator Configuration 56 | 57 | * `input_generator` [str]: Input generator type. 58 | Only one option is currently supported - "random" (default) 59 | * `input_gen_seed` [int]: Input generation seed. 60 | Will use a random seed if set to zero. 61 | * `input_gen_entropy_bits` [int]: Entropy of the random values created by the input generator. 62 | * `inputs_per_class` [int]: Number of inputs per input class. 63 | 64 | # Executor Configuration 65 | 66 | * `executor` [str]: Executor type. 67 | Only one option is currently supported - "x86-intel" (default). 68 | * `executor_mode` [str]: Hardware trace collection mode. 69 | Available options: 'P+P' - prime and probe; 'F+R' - flush and reload; 'E+R' - evict and reload. 70 | * `executor_warmups` [int]: Number of warmup rounds executed before starting to collect hardware traces. 71 | * `executor_repetitions` [int]: Number of repetitions while collecting hardware traces. 72 | * `executor_taskset` [int]: CPU number on which the executor is running test cases. 73 | * `enable_ssbp_patch` [bool]: Enable a patch against Speculative Store Bypass (Intel-only). 74 | Enabled by default. 75 | * `enable_pre_run_flush` [bool]: If enabled, the executor will do its best to flush the microarchitectural state before running test cases. 76 | Enabled by default. 77 | * `enable_assist_page` [bool]: If enabled, only of the sandbox memory pages will have the accessed bit set to zero, which will cause a microcode assist on the fist load/store to this page. 78 | Disabled by default. 79 | 80 | # Analyser Configuration 81 | 82 | * `analyser` [str]: Analyser type. 83 | Only one option is currently supported - "equivalence-classes" (default). 84 | * `analyser_permit_subsets` [bool]: If enabled, the analyser will not label hardware traces as mismatching if they form a subset relation. 85 | Enabled by default. 86 | 87 | # Coverage Configuration 88 | 89 | * `coverage_type` [str]: Coverage type. 90 | Available options: 91 | 'none' - disable coverage tracking; 92 | 'dependent-pairs' - coverage of pairs of dependent instructions. 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WE MOVED! 2 | 3 | This repository was migrated back to the original repo. For the most recent version of the project, see [Revizor from Microsoft](https://github.com/microsoft/sca-fuzzer). 4 | 5 | ---- 6 | 7 | # Revizor 8 | 9 | This is Revizor, a microarchitectural fuzzer. 10 | It is a rather unconventional fuzzer as, instead of finding bugs in programs, Revizor searches for microarchitectural vulnerabilities in CPUs. 11 | 12 | What is a microarchitectural vulnerability? 13 | In the context of Revizor, it is a violation of out expectations about the CPU behavior, expressed as contract violations (see [Contracts](https://arxiv.org/abs/2006.03841)). 14 | The most prominent examples would be [Spectre](https://spectreattack.com/) and [Meltdown](https://meltdownattack.com/). 15 | Alternatively, a "bug" could also be in a form of a microarchitectural backdoor or an unknown optimization, although we are yet to encounter one of those. 16 | 17 | See our [Technical Report](https://arxiv.org/abs/2105.06872) for details. 18 | 19 | 20 | **Origin**: This is an independently developed and improved fork of [SCA-Fuzzer from Microsoft](https://github.com/microsoft/sca-fuzzer). 21 | 22 | # Getting Started 23 | 24 | **Note:** If you find missing or confusing explanations, or a bug in Revizor, don't hesitate to open an issue. 25 | 26 | **Warning**: Revizor executes randomly generated code in kernel space. 27 | As you can imagine, things could go wrong. 28 | We did our best to avoid it and to make Revizor stable, but still, no software is perfect. 29 | Make sure you're not running these experiments on an important machine. 30 | 31 | ## Requirements & Dependencies 32 | 33 | 1. Hardware Requirements 34 | 35 | So far, Revizor supports only Intel CPU. It was tested on Intel Core i7-6700 and i7-9700, but it should work on any other Intel CPU just as well. 36 | 37 | 1. Software Requirements 38 | 39 | * Linux v5.6+ (tested on Linux v5.6.6-300 and v5.6.13-100; there is a good chance it will work on other versions as well, but it's not guaranteed). 40 | * Linux Kernel Headers 41 | * Python 3.7+ 42 | * [Unicorn 1.0.2+](https://www.unicorn-engine.org/docs/) 43 | * Python bindings to Unicorn: 44 | ```shell 45 | pip3 install --user unicorn 46 | 47 | # OR, if installed from sources 48 | cd bindings/python 49 | sudo make install 50 | ``` 51 | * Python packages `pyyaml`, `types-pyyaml`, `numpy`, `iced-x86`: 52 | ```shell 53 | pip3 install --user pyyaml types-pyyaml numpy iced-x86 54 | ``` 55 | 56 | 1. Software Requirements for Revizor Development 57 | 58 | Tests: 59 | * [Bash Automated Testing System](https://bats-core.readthedocs.io/en/latest/index.html) 60 | * [mypy](https://mypy.readthedocs.io/en/latest/getting_started.html#installing-and-running-mypy) 61 | * `GNU datamash` 62 | 63 | Documentation: 64 | * [pdoc3](https://pypi.org/project/pdoc3/) 65 | 66 | 1. (Optional) System Configuration 67 | 68 | For more stable results, disable hyperthreading (there's usually a BIOS option for it). 69 | If you do not disable hyperthreading, you will see a warning every time you invoke Revizor; you can ignore it. 70 | 71 | Optionally (and it *really* is optional), you can boot the kernel on a single core by adding `-maxcpus=1` to the boot parameters ([how to add a boot parameter](https://wiki.ubuntu.com/Kernel/KernelBootParameters)). 72 | 73 | In addition, you might want to stop any other actively-running software on the tested machine. We never encountered issues with it, but it might be useful. 74 | 75 | ## Installation 76 | 77 | 1. Get the x86-64 ISA description: 78 | ```bash 79 | cd src/x86/isa_loader 80 | ./get_spec.py --extensions BASE SSE SSE2 CLFLUSHOPT CLFSH 81 | ``` 82 | 83 | 2. Install the executor kernel module: 84 | ```bash 85 | cd src/x86/executor 86 | make uninstall # the command will give an error message, but it's ok! 87 | make clean 88 | make 89 | make install 90 | ``` 91 | 92 | ## Running Tests 93 | 94 | ```bash 95 | cd src/tests 96 | ./runtests.sh 97 | ``` 98 | 99 | If a few (up to 3) "Detection" tests fail, it's fine, you might just have a slightly different microarchitecture. But if other tests fail - something is broken. 100 | 101 | ## Basic Usability Test 102 | 103 | 1. Fuzz in a violation-free configuration: 104 | ```bash 105 | ./cli.py fuzz -s x86/isa_spec/base.json -i 50 -n 100 -c tests/test-nondetection.yaml 106 | ``` 107 | 108 | No violations should be detected. 109 | 110 | 2. Fuzz in a configuration with a known contract violation (Spectre V1): 111 | ```bash 112 | ./cli.py fuzz -s x86/isa_spec/base.json -i 20 -n 1000 -c tests/test-detection.yaml 113 | ``` 114 | 115 | A violation should be detected within a few minutes, with a message similar to this: 116 | 117 | ``` 118 | ================================ Violations detected ========================== 119 | Contract trace (hash): 120 | 121 | 0111010000011100111000001010010011110101110011110100000111010110 122 | Hardware traces: 123 | Inputs [907599882]: 124 | _____^______^______^___________________________________________^ 125 | Inputs [2282448906]: 126 | ___________________^_____^___________________________________^_^ 127 | 128 | ``` 129 | 130 | Congratulations, you just found your first Spectre! You can find the violating test case in `generated.asm`. 131 | 132 | # Fuzzing Example 133 | 134 | To start a real fuzzing campaign, write your own configuration file (see description [here](docs/config.md) and an example config [here](src/tests/big-fuzz.yaml)), and launch the fuzzer. 135 | 136 | Below is a example launch command, which will start a 24-hour fuzzing session, with 50 input classes per test case: 137 | 138 | ```shell 139 | ./cli.py fuzz -s x86/isa_spec/base.json -c tests/big-fuzz.yaml -i 50 -n 100000000 --timeout 86400 -w `pwd` --nonstop 140 | ``` 141 | 142 | # Command line interface 143 | 144 | The fuzzer is controlled via a single command line interface `cli.py` (located in `src/cli.py`). It accepts the following arguments: 145 | 146 | * `-s, --instruction-set PATH` - path to the ISA description file 147 | * `-c, --config PATH` - path to the fuzzing configuration file 148 | * `-n , --num-test-cases N` - number of test cases to be tested 149 | * `-i , --num-inputs N` - number of input classes per test case. The number of actual inputs = input classes * inputs_per_class, which is a configuration option 150 | * `-t , --testcase PATH` - use an existing test case instead of generating random test cases 151 | * `--timeout TIMEOUT` - run fuzzing with a time limit [seconds] 152 | * `--nonstop` - don't stop after detecting a contract violation 153 | * `-w` - working directory where the detected violations will be stored 154 | 155 | # Documentation 156 | 157 | For more details, see [docs/_main.md](docs/_main.md). 158 | -------------------------------------------------------------------------------- /src/isa_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | import json 8 | from typing import Dict 9 | from interfaces import OT, InstructionSetAbstract, OperandSpec, InstructionSpec 10 | from config import CONF 11 | 12 | 13 | class InstructionSet(InstructionSetAbstract): 14 | ot_str_to_enum = { 15 | "REG": OT.REG, 16 | "MEM": OT.MEM, 17 | "IMM": OT.IMM, 18 | "LABEL": OT.LABEL, 19 | "AGEN": OT.AGEN, 20 | "FLAGS": OT.FLAGS, 21 | "COND": OT.COND, 22 | } 23 | 24 | def __init__(self, filename: str, include_categories=None): 25 | self.instructions = [] 26 | self.init_from_file(filename) 27 | self.reduce(include_categories) 28 | self.dedup() 29 | super().__init__(filename, include_categories) 30 | 31 | def init_from_file(self, filename: str): 32 | with open(filename, "r") as f: 33 | root = json.load(f) 34 | for instruction_node in root: 35 | instruction = InstructionSpec() 36 | instruction.name = instruction_node["name"] 37 | instruction.category = instruction_node["category"] 38 | instruction.control_flow = instruction_node["control_flow"] 39 | 40 | for op_node in instruction_node["operands"]: 41 | op = self.parse_operand(op_node, instruction) 42 | instruction.operands.append(op) 43 | if op.magic_value: 44 | instruction.has_magic_value = True 45 | 46 | for op_node in instruction_node["implicit_operands"]: 47 | op = self.parse_operand(op_node, instruction) 48 | instruction.implicit_operands.append(op) 49 | 50 | self.instructions.append(instruction) 51 | 52 | def parse_operand(self, op: Dict, parent: InstructionSpec) -> OperandSpec: 53 | op_type = self.ot_str_to_enum[op["type_"]] 54 | spec = OperandSpec(op["values"], op_type, op["src"], op["dest"]) 55 | spec.width = op["width"] 56 | 57 | if op_type == OT.MEM: 58 | parent.has_mem_operand = True 59 | if spec.dest: 60 | parent.has_write = True 61 | 62 | return spec 63 | 64 | def reduce(self, include_categories): 65 | """ Remove unsupported instructions and operand values """ 66 | 67 | def is_supported(spec: InstructionSpec): 68 | if CONF._no_generation: 69 | # if we use an existing test case, then instruction filterring is irrelevant 70 | return True 71 | 72 | if include_categories and spec.category not in include_categories: 73 | return False 74 | 75 | if spec.name in CONF.instruction_blocklist: 76 | return False 77 | 78 | for operand in spec.operands: 79 | if operand.type == OT.MEM and operand.values \ 80 | and operand.values[0] in CONF.gpr_blocklist: 81 | return False 82 | 83 | for implicit_operand in spec.implicit_operands: 84 | assert implicit_operand.type != OT.LABEL # I know no such instructions 85 | if implicit_operand.type == OT.MEM and \ 86 | implicit_operand.values[0] in CONF.gpr_blocklist: 87 | return False 88 | 89 | if implicit_operand.type == OT.REG and \ 90 | implicit_operand.values[0] in CONF.gpr_blocklist: 91 | assert len(implicit_operand.values) == 1 92 | return False 93 | return True 94 | 95 | skip_list = [] 96 | for s in self.instructions: 97 | # Unsupported instructions 98 | if not is_supported(s): 99 | skip_list.append(s) 100 | continue 101 | 102 | skip_pending = False 103 | for op in s.operands: 104 | if op.type == OT.REG: 105 | choices = list(set(op.values) - set(CONF.gpr_blocklist)) 106 | if not choices: 107 | skip_pending = True 108 | break 109 | op.values = choices 110 | 111 | # FIXME: temporary disabled generation of higher reg. bytes for x86 112 | for i, reg in enumerate(op.values): 113 | if reg[-1] == 'H': 114 | op.values[i] = reg.replace( 115 | 'H', 116 | 'L', 117 | ) 118 | 119 | if skip_pending: 120 | skip_list.append(s) 121 | 122 | # remove the unsupported 123 | for s in skip_list: 124 | self.instructions.remove(s) 125 | 126 | # set parameters 127 | for inst in self.instructions: 128 | if inst.control_flow: 129 | if inst.category == "BASE-UNCOND_BR": 130 | self.has_unconditional_branch = True 131 | else: 132 | self.has_conditional_branch = True 133 | elif inst.has_mem_operand: 134 | if inst.has_write: 135 | self.has_writes = True 136 | else: 137 | self.has_reads = True 138 | 139 | def dedup(self): 140 | """ 141 | Instruction set spec may contain several copies of the same instruction. 142 | Remove them. 143 | """ 144 | skip_list = set() 145 | for i in range(len(self.instructions)): 146 | for j in range(i + 1, len(self.instructions)): 147 | inst1 = self.instructions[i] 148 | inst2 = self.instructions[j] 149 | if inst1.name == inst2.name and len(inst1.operands) == len(inst2.operands): 150 | match = True 151 | for k, op1 in enumerate(inst1.operands): 152 | op2 = inst2.operands[k] 153 | 154 | if op1.type != op2.type: 155 | match = False 156 | continue 157 | 158 | if op1.values != op2.values: 159 | match = False 160 | continue 161 | 162 | if op1.width != op2.width and op1.type != OT.IMM: 163 | match = False 164 | continue 165 | 166 | # assert op1.src == op2.src 167 | # assert op1.dest == op2.dest 168 | 169 | if match: 170 | skip_list.add(inst1) 171 | 172 | for s in skip_list: 173 | self.instructions.remove(s) 174 | -------------------------------------------------------------------------------- /src/tests/unittests/unit_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Microsoft Corporation 3 | SPDX-License-Identifier: MIT 4 | """ 5 | import unittest 6 | import sys 7 | 8 | sys.path.insert(0, '..') 9 | from model import TaintTracker 10 | from interfaces import Instruction, RegisterOperand, MemoryOperand, InputTaint, LabelOperand, \ 11 | FlagsOperand 12 | 13 | 14 | class X86UnicornModelTest(unittest.TestCase): 15 | 16 | def test_dependency_tracking(self): 17 | tracker = TaintTracker([]) 18 | 19 | # reg -> reg 20 | tracker.start_instruction(Instruction("ADD") 21 | .add_op(RegisterOperand("RAX", 64, True, True)) 22 | .add_op(RegisterOperand("RBX", 64, True, False))) # yapf: disable 23 | tracker._finalize_instruction() 24 | self.assertCountEqual(tracker.src_regs, ["A", "B"]) 25 | self.assertCountEqual(tracker.dest_regs, ["A"]) 26 | self.assertCountEqual(tracker.reg_dependencies['A'], ['A', 'B']) 27 | 28 | # chain of dependencies 29 | tracker.start_instruction(Instruction("MOV") 30 | .add_op(RegisterOperand("RCX", 64, False, True)) 31 | .add_op(RegisterOperand("RAX", 64, True, False))) # yapf: disable 32 | tracker._finalize_instruction() 33 | self.assertCountEqual(tracker.reg_dependencies['A'], ['A', 'B']) 34 | self.assertCountEqual(tracker.reg_dependencies['C'], ['A', 'B', 'C']) 35 | 36 | # memory -> reg 37 | tracker.start_instruction(Instruction("MOV") 38 | .add_op(RegisterOperand("RDX", 64, False, True)) 39 | .add_op(MemoryOperand("RCX", 64, True, False))) # yapf: disable 40 | tracker.track_memory_access(0x87, 8, False) 41 | tracker._finalize_instruction() 42 | self.assertCountEqual(tracker.reg_dependencies['D'], ['0x80', '0x88', 'D']) 43 | 44 | # reg -> mem 45 | tracker.start_instruction(Instruction("MOV") 46 | .add_op(MemoryOperand("RAX", 64, False, True)) 47 | .add_op(RegisterOperand("RSI", 64, True, False))) # yapf: disable 48 | tracker.track_memory_access(0x80, 8, True) 49 | tracker._finalize_instruction() 50 | self.assertCountEqual(tracker.mem_dependencies['0x80'], ['0x80', 'SI']) 51 | 52 | # store -> load 53 | tracker.start_instruction(Instruction("MOV") 54 | .add_op(RegisterOperand("RDI", 64, False, True)) 55 | .add_op(MemoryOperand("RAX", 64, True, False))) # yapf: disable 56 | tracker.track_memory_access(0x80, 8, False) 57 | tracker._finalize_instruction() 58 | self.assertCountEqual(tracker.reg_dependencies['DI'], ['SI', 'DI', '0x80']) 59 | 60 | def test_tainting(self): 61 | tracker = TaintTracker([]) 62 | 63 | # Initial dependency 64 | tracker.start_instruction(Instruction("ADD") 65 | .add_op(RegisterOperand("RAX", 64, True, True)) 66 | .add_op(RegisterOperand("RBX", 64, True, False))) # yapf: disable 67 | tracker._finalize_instruction() 68 | tracker.start_instruction(Instruction("MOV") 69 | .add_op(MemoryOperand("RAX", 64, False, True)) 70 | .add_op(RegisterOperand("RAX", 64, True, False))) # yapf: disable 71 | tracker.track_memory_access(0x80, 8, True) 72 | tracker._finalize_instruction() 73 | 74 | # Taint memory address 75 | tracker.start_instruction(Instruction("MOV") 76 | .add_op(RegisterOperand("RBX", 64, True, False)) 77 | .add_op(MemoryOperand("RCX + 8 -16", 64, False, True)) 78 | ) # yapf: disable 79 | tracker.track_memory_access(0x80, 8, False) 80 | tracker.taint_memory_access_address() 81 | taint: InputTaint = tracker.get_taint() 82 | reg_offset = taint.register_start 83 | 84 | self.assertCountEqual(tracker.mem_dependencies['0x80'], ['A', 'B', '0x80']) 85 | self.assertCountEqual(tracker.tainted_labels, {'C'}) 86 | self.assertEqual(taint[reg_offset + 2], True) # RCX 87 | 88 | # Taint PC 89 | tracker.tainted_labels = set() 90 | tracker.start_instruction(Instruction("CMPC") 91 | .add_op(RegisterOperand("RAX", 64, True, False)) 92 | .add_op(RegisterOperand("RDX", 64, True, False)) 93 | .add_op(FlagsOperand(["w", "", "", "", "", "", "", "", ""]), True) 94 | ) # yapf: disable 95 | tracker._finalize_instruction() 96 | jmp_instruction = Instruction("JC").add_op(LabelOperand(".bb0"))\ 97 | .add_op(FlagsOperand(["r", "", "", "", "", "", "", "", ""]), True)\ 98 | .add_op(RegisterOperand("RIP", 64, True, True), True) 99 | jmp_instruction.control_flow = True 100 | tracker.start_instruction(jmp_instruction) 101 | tracker.taint_pc() 102 | taint: InputTaint = tracker.get_taint() 103 | self.assertEqual(taint[reg_offset], True) # RAX - through flags 104 | self.assertEqual(taint[reg_offset + 1], True) # RBX - through flags + register 105 | self.assertEqual(taint[reg_offset + 3], True) # RDX - through flags 106 | 107 | # Taint load value 108 | tracker.tainted_labels = set() 109 | tracker.start_instruction(Instruction("MOV") 110 | .add_op(RegisterOperand("RBX", 64, False, True)) 111 | .add_op(MemoryOperand("RCX", 64, True, False)) 112 | ) # yapf: disable 113 | tracker.track_memory_access(0x80, 8, is_write=False) 114 | tracker.taint_memory_load() 115 | taint: InputTaint = tracker.get_taint() 116 | # 0x80 -> A -> [A, B] 117 | self.assertEqual(taint[reg_offset + 0], True) 118 | self.assertEqual(taint[reg_offset + 1], True) 119 | 120 | def test_label_to_taint(self): 121 | tracker = TaintTracker([]) 122 | tracker.tainted_labels = {'0x0', '0x40', '0x640', 'D', 'SI', '8', '14', 'DF', 'RIP'} 123 | taint: InputTaint = tracker.get_taint() 124 | register_start = taint.register_start 125 | taint_size = taint.size 126 | 127 | expected = [False for i in range(taint_size)] 128 | expected[0] = True # 0x0 129 | expected[8] = True # 0x40 130 | expected[200] = True # 640 131 | expected[register_start + 3] = True # D 132 | expected[register_start + 4] = True # SI 133 | expected[register_start + 6] = True # DF - flags 134 | # 8, 14, RIP - not a part of the input 135 | 136 | self.assertListEqual(list(taint), expected) 137 | 138 | 139 | 140 | if __name__ == '__main__': 141 | unittest.main() 142 | -------------------------------------------------------------------------------- /src/tests/acceptance.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | INSTRUCTION_SET='x86/isa_spec/base.json' 3 | 4 | EXTENDED_TESTS=0 5 | cli_opt="python3 -OO ./cli.py" 6 | 7 | @test "Model and Executor are initialized with the same values" { 8 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/model_match.asm -c tests/model_match.yaml -i 100" 9 | echo "$output" 10 | [ "$status" -eq 0 ] 11 | [ "$output" = "" ] 12 | } 13 | 14 | @test "Model and Executor are initialized with the same FLAGS value" { 15 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/model_flags_match.asm -c tests/model_match.yaml -i 100" 16 | echo "$output" 17 | [ "$status" -eq 0 ] 18 | [ "$output" = "" ] 19 | } 20 | 21 | function run_without_violation { 22 | local cmd=$1 23 | tmp_config=$(mktemp) 24 | cat << EOF >> $tmp_config 25 | logging_modes: 26 | - 27 | EOF 28 | run bash -c "$cmd -c $tmp_config" 29 | echo "$output" 30 | [ "$status" -eq 0 ] 31 | [ "$output" = "" ] 32 | rm $tmp_config 33 | } 34 | 35 | @test "Fuzzing: A sequence of NOPs" { 36 | run_without_violation "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/nops.asm -i 100" 37 | } 38 | 39 | @test "Fuzzing: A sequence of direct jumps" { 40 | run_without_violation "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/direct_jumps.asm -i 100" 41 | } 42 | 43 | @test "Fuzzing: A long in-reg test case" { 44 | run_without_violation "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/large_arithmetic.asm -i 10" 45 | } 46 | 47 | @test "Fuzzing: A sequence of calls" { 48 | run_without_violation "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/calls.asm -i 100" 49 | } 50 | 51 | @test "Detection: Spectre V1 - BCB load - P" { 52 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v1.asm -i 20" 53 | echo "$output" 54 | [ "$status" -eq 0 ] 55 | [[ "$output" = *"=== Violations detected ==="* ]] 56 | } 57 | 58 | @test "Detection: Spectre V1 - BCB load - N" { 59 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v1.asm -c tests/ct-cond.yaml -i 20" 60 | echo "$output" 61 | [ "$status" -eq 0 ] 62 | [[ "$output" != *"=== Violations detected ==="* ]] 63 | } 64 | 65 | @test "Detection: Spectre V1.1 - BCB store" { 66 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v1.1.asm -i 100" 67 | echo "$output" 68 | [ "$status" -eq 0 ] 69 | [[ "$output" = *"=== Violations detected ==="* ]] 70 | } 71 | 72 | @test "Detection: Spectre V2 - BTI - P" { 73 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v2.asm -i 20" 74 | echo "$output" 75 | [ "$status" -eq 0 ] 76 | [[ "$output" = *"=== Violations detected ==="* ]] 77 | } 78 | 79 | @test "Detection: Spectre V4 - SSBP - P" { 80 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v4.asm -c tests/ct-seq-ssbp-patch-off.yaml -i 200" 81 | echo "$output" 82 | [ "$status" -eq 0 ] 83 | [[ "$output" = *"=== Violations detected ==="* ]] 84 | } 85 | 86 | @test "Detection: Spectre V4 - SSBP - N (patch off)" { 87 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v4.asm -c tests/ct-bpas-ssbp-patch-off.yaml -i 200" 88 | echo "$output" 89 | [ "$status" -eq 0 ] 90 | [[ "$output" != *"=== Violations detected ==="* ]] 91 | } 92 | 93 | @test "Detection: Spectre V4 - SSBP - N (patch on)" { 94 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v4.asm -i 200" 95 | echo "$output" 96 | [ "$status" -eq 0 ] 97 | [[ "$output" != *"=== Violations detected ==="* ]] 98 | } 99 | 100 | @test "Detection: Spectre V5-ret" { 101 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_ret.asm -i 10" 102 | echo "$output" 103 | [ "$status" -eq 0 ] 104 | [[ "$output" = *"=== Violations detected ==="* ]] 105 | } 106 | 107 | @test "Detection: Nested misprediction" { 108 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v4_n2.asm -i 200 -c tests/ct-bpas-n1-ssbp-patch-off.yaml" 109 | echo "$output" 110 | [ "$status" -eq 0 ] 111 | [[ "$output" = *"=== Violations detected ==="* ]] 112 | 113 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/spectre_v4_n2.asm -i 200 -c tests/ct-bpas-ssbp-patch-off.yaml" 114 | echo "$output" 115 | [ "$status" -eq 0 ] 116 | [[ "$output" != *"=== Violations detected ==="* ]] 117 | } 118 | 119 | @test "Detection: MDS-SB" { 120 | if cat /proc/cpuinfo | grep "mds" ; then 121 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/mds.asm -i 100 -c tests/mds.yaml" 122 | echo "$output" 123 | [ "$status" -eq 0 ] 124 | [[ "$output" = *"=== Violations detected ==="* ]] 125 | else 126 | run bash -c "$cli_opt fuzz -s $INSTRUCTION_SET -t tests/lvi.asm -i 100 -c tests/mds.yaml" 127 | echo "$output" 128 | [ "$status" -eq 0 ] 129 | [[ "$output" = *"=== Violations detected ==="* ]] 130 | fi 131 | } 132 | 133 | @test "False Positive: Input-independent branch misprediction" { 134 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t tests/spectre_v1_independent.asm -i 100" 135 | echo "$output" 136 | [ "$status" -eq 0 ] 137 | [[ "$output" != *"=== Violations detected ==="* ]] 138 | } 139 | 140 | @test "Analyser: Priming" { 141 | skip 142 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t tests/priming.asm -i 100 -c tests/priming.yaml" 143 | echo "$output" 144 | [ "$status" -eq 0 ] 145 | [[ "$output" == *"Priming"* ]] 146 | [[ "$output" != *"=== Violations detected ==="* ]] 147 | } 148 | 149 | @test "Model: ARCH-SEQ" { 150 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t tests/spectre_v1_arch.asm -i 20 -c tests/arch-seq.yaml" 151 | echo "$output" 152 | [ "$status" -eq 0 ] 153 | [[ "$output" = *"=== Violations detected ==="* ]] 154 | } 155 | 156 | @test "Model: Rollback on LFENCE and spec. window" { 157 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t tests/rollback_fence_and_expire.asm -i 10 -c tests/rollback_fence_and_expire.yaml" 158 | echo "$output" 159 | [ "$status" -eq 0 ] 160 | [[ "$output" != *"[s]"* ]] 161 | } 162 | 163 | # ================================================================================================== 164 | # Extended tests - take long time, but test deeper 165 | # ================================================================================================== 166 | @test "Extended: False positives from generated samples" { 167 | if [ $EXTENDED_TESTS -eq 0 ]; then 168 | skip 169 | fi 170 | 171 | for test_case in tests/generated-fp/* ; do 172 | echo "Testing $test_case" 173 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t $test_case -i 10000 -c tests/ct-cond-bpas.yaml" 174 | echo "$output" 175 | [ "$status" -eq 0 ] 176 | [[ "$output" != *"=== Violations detected ==="* ]] 177 | done 178 | } 179 | 180 | @test "Priming: False Positive due to small min_primer_size" { 181 | if [ $EXTENDED_TESTS -eq 0 ]; then 182 | skip 183 | fi 184 | 185 | run bash -c "./cli.py fuzz -s $INSTRUCTION_SET -t tests/generated/priming-19-03-21.asm -i 500 -c tests/generated/priming-19-03-21.yaml" 186 | echo "$output" 187 | [ "$status" -eq 0 ] 188 | [[ "$output" != *"=== Violations detected ==="* ]] 189 | } -------------------------------------------------------------------------------- /src/executor.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Executor Interface 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | import subprocess 8 | import os.path 9 | import csv 10 | import numpy as np 11 | from collections import Counter 12 | from typing import List 13 | from interfaces import CombinedHTrace, Input, TestCase, Executor, Optional 14 | 15 | from config import CONF, ConfigException 16 | from service import LOGGER 17 | 18 | 19 | def write_to_sysfs_file(value, path: str) -> None: 20 | subprocess.run(f"sudo bash -c 'echo -n {value} > {path}'", shell=True, check=True) 21 | 22 | 23 | def write_to_sysfs_file_bytes(value: bytes, path: str) -> None: 24 | with open(path, "wb") as f: 25 | f.write(value) 26 | 27 | 28 | class X86IntelExecutor(Executor): 29 | previous_num_inputs: int = 0 30 | 31 | def __init__(self): 32 | super().__init__() 33 | # check the execution environment: is SMT disabled? 34 | smt_on: Optional[bool] = None 35 | try: 36 | out = subprocess.run("lscpu", shell=True, check=True, capture_output=True) 37 | except subprocess.CalledProcessError: 38 | LOGGER.error("Could not check if hyperthreading is enabled.\n" 39 | " Is lscpu installed?") 40 | for line in out.stdout.decode().split("\n"): 41 | if line.startswith("Thread(s) per core:"): 42 | if line[-1] == "1": 43 | smt_on = False 44 | else: 45 | smt_on = True 46 | if smt_on is None: 47 | LOGGER.waring("executor", "Could not check if SMT is on.") 48 | if smt_on: 49 | LOGGER.waring("executor", "SMT is on! You may experience false positives.") 50 | 51 | # disable prefetching 52 | subprocess.run('sudo modprobe msr', shell=True, check=True) 53 | subprocess.run('sudo wrmsr -a 0x1a4 15', shell=True, check=True) 54 | 55 | # is kernel module ready? 56 | if not os.path.isfile("/sys/x86_executor/trace"): 57 | LOGGER.error("x86 executor: kernel module not loaded") 58 | 59 | # initialize the kernel module 60 | write_to_sysfs_file(CONF.executor_warmups, '/sys/x86_executor/warmups') 61 | write_to_sysfs_file("1" if CONF.enable_ssbp_patch else "0", 62 | "/sys/x86_executor/enable_ssbp_patch") 63 | write_to_sysfs_file("1" if CONF.enable_pre_run_flush else "0", 64 | "/sys/x86_executor/enable_pre_run_flush") 65 | write_to_sysfs_file("1" if CONF.enable_assist_page else "0", "/sys/x86_executor/enable_mds") 66 | write_to_sysfs_file(CONF.executor_mode, "/sys/x86_executor/measurement_mode") 67 | 68 | def load_test_case(self, test_case: TestCase): 69 | with open(test_case.bin_path, "rb") as f: 70 | write_to_sysfs_file_bytes(f.read(), "/sys/x86_executor/test_case") 71 | 72 | def trace_test_case(self, inputs: List[Input], repetitions: int = 0) \ 73 | -> List[CombinedHTrace]: 74 | # make sure it's not a dummy call 75 | if not inputs: 76 | return [] 77 | 78 | if repetitions == 0: 79 | repetitions = CONF.executor_repetitions 80 | 81 | # convert the inputs into a byte sequence 82 | byte_inputs = [i.tobytes() for i in inputs] 83 | byte_inputs_merged = bytes().join(byte_inputs) 84 | 85 | # protocol of loading inputs (must be in this order): 86 | # 1) Announce the number of inputs 87 | write_to_sysfs_file(str(len(inputs)), "/sys/x86_executor/n_inputs") 88 | # 2) Load the inputs 89 | write_to_sysfs_file_bytes(byte_inputs_merged, "/sys/x86_executor/inputs") 90 | # 3) Check that the load was successful 91 | with open('/sys/x86_executor/inputs', 'r') as f: 92 | if f.readline() != '1\n': 93 | LOGGER.error("Failure loading inputs!") 94 | 95 | # run experiments and load the results 96 | all_results: np.ndarray = np.ndarray(shape=(len(inputs), repetitions, 4), dtype=np.uint64) 97 | for rep in range(repetitions): 98 | # executor prints results in reverse, so we begin from the end 99 | input_id = len(inputs) - 1 100 | reading_finished = False 101 | 102 | # executor prints results in batches, hence we have to call it several times, 103 | # until we find the `done` keyword in the output 104 | while not reading_finished: 105 | output = subprocess.check_output( 106 | f"taskset -c {CONF.executor_taskset} cat /sys/x86_executor/trace", shell=True) 107 | reader = csv.reader(output.decode().split("\n")) 108 | for row in reader: 109 | if not row: 110 | continue 111 | if 'done' in row: 112 | reading_finished = True 113 | break 114 | 115 | all_results[input_id][rep][0] = int(row[0]) 116 | all_results[input_id][rep][1] = int(row[1]) 117 | all_results[input_id][rep][2] = int(row[2]) 118 | all_results[input_id][rep][3] = int(row[3]) 119 | input_id -= 1 120 | 121 | # simple case - no merging required 122 | if repetitions == 1: 123 | if self.coverage: 124 | self.coverage.executor_hook([r[1:] for r in all_results[0]]) 125 | return [int(r[0]) for r in all_results[0]] 126 | 127 | threshold_outliers = min(CONF.executor_max_outliers, repetitions - 1) 128 | traces = [0 for _ in inputs] 129 | pfc_readings = np.ndarray(shape=(len(inputs), 3), dtype=int) 130 | 131 | # merge the results of repeated measurements 132 | for input_id, input_results in enumerate(all_results): 133 | # find the max value of each perf counter for each input 134 | for pfc_id in range(0, 2): 135 | pfc_readings[input_id][pfc_id] = max([res[pfc_id + 1] for res in input_results]) 136 | 137 | # remove outliers and merge hardware traces 138 | counter = Counter() 139 | for result in input_results: 140 | trace = int(result[0]) 141 | counter[trace] += 1 142 | if counter[trace] == threshold_outliers + 1: 143 | # merge the trace if we observed it sufficiently many time 144 | # (i.e., if we can conclude it's not noise) 145 | traces[input_id] |= trace 146 | 147 | if self.coverage: 148 | self.coverage.executor_hook(pfc_readings) 149 | 150 | return traces 151 | 152 | def read_base_addresses(self): 153 | with open('/sys/x86_executor/print_sandbox_base', 'r') as f: 154 | sandbox_base = f.readline() 155 | with open('/sys/x86_executor/print_code_base', 'r') as f: 156 | code_base = f.readline() 157 | return int(sandbox_base, 16), int(code_base, 16) 158 | 159 | 160 | def get_executor() -> Executor: 161 | options = { 162 | 'x86-intel': X86IntelExecutor, 163 | } 164 | if CONF.executor not in options: 165 | ConfigException("unknown executor in config.py") 166 | return options[CONF.executor]() 167 | -------------------------------------------------------------------------------- /src/postprocessor.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: All kinds of postprocessing actions performed after a violation has been detected. 3 | Currently, it's a stripped-down version of the main fuzzer, modified to find the minimal 4 | set of inputs that reproduce the vulnerability and to minimize the test case. 5 | 6 | Copyright (C) Microsoft Corporation 7 | SPDX-License-Identifier: MIT 8 | """ 9 | from subprocess import run 10 | from shutil import copy 11 | from fuzzer import Fuzzer 12 | from typing import List 13 | from interfaces import HTrace, EquivalenceClass, Input, TestCase 14 | from config import CONF 15 | 16 | 17 | class Postprocessor: 18 | 19 | def __init__(self, instruction_set_spec): 20 | self.instruction_set_spec = instruction_set_spec 21 | 22 | def _get_all_violations(self, fuzzer: Fuzzer, test_case: TestCase, 23 | inputs: List[Input]) -> List[EquivalenceClass]: 24 | # Initial measurement 25 | fuzzer.model.load_test_case(test_case) 26 | fuzzer.executor.load_test_case(test_case) 27 | ctraces = fuzzer.model.trace_test_case(inputs, CONF.model_max_nesting) 28 | htraces: List[HTrace] = fuzzer.executor.trace_test_case(inputs) 29 | 30 | # Check for violations 31 | violations: List[EquivalenceClass] = fuzzer.analyser.filter_violations( 32 | inputs, ctraces, htraces, stats=True) 33 | if not violations: 34 | return [] 35 | if CONF.no_priming: 36 | return violations 37 | 38 | # Try priming the inputs that disagree with the other ones within the same eq. class 39 | true_violations = [] 40 | while violations: 41 | violation: EquivalenceClass = violations.pop() 42 | if fuzzer.survives_priming(violation, inputs): 43 | true_violations.append(violation) 44 | 45 | return true_violations 46 | 47 | def _get_test_case_from_instructions(self, fuzzer, instructions: List[str]) -> TestCase: 48 | minimized_asm = "/tmp/minimised.asm" 49 | run(f"touch {minimized_asm}", shell=True, check=True) 50 | with open(minimized_asm, "w+") as f: 51 | f.seek(0) # is it necessary?? 52 | for line in instructions: 53 | f.write(line) 54 | f.truncate() # is it necessary?? 55 | return fuzzer.generator.parse_existing_test_case(minimized_asm) 56 | 57 | def _probe_test_case(self, fuzzer: Fuzzer, test_case: TestCase, inputs: List[Input], 58 | modifier) -> TestCase: 59 | with open(test_case.asm_path, "r") as f: 60 | instructions = f.readlines() 61 | 62 | cursor = len(instructions) 63 | 64 | # Try removing instructions, one at a time 65 | while True: 66 | cursor -= 1 67 | line = instructions[cursor].strip() 68 | 69 | # Did we reach the header? 70 | if line == ".test_case_enter:": 71 | break 72 | 73 | # Preserve instructions used for sandboxing, fences, and labels 74 | if not line or \ 75 | "instrumentation" in line or \ 76 | "LFENCE" in line or \ 77 | line[0] == '.': 78 | continue 79 | 80 | # Create a test case with one line missing 81 | tmp_instructions = modifier(instructions, cursor) 82 | tmp_test_case = self._get_test_case_from_instructions(fuzzer, tmp_instructions) 83 | 84 | # Run and check if the vuln. is still there 85 | retries = 1 86 | for _ in range(0, retries): 87 | violations = self._get_all_violations(fuzzer, tmp_test_case, inputs) 88 | if violations: 89 | break 90 | if violations: 91 | print(".", end="", flush=True) 92 | instructions = tmp_instructions 93 | else: 94 | print("-", end="", flush=True) 95 | 96 | new_test_case = self._get_test_case_from_instructions(fuzzer, instructions) 97 | return new_test_case 98 | 99 | def minimize(self, test_case_asm: str, outfile: str, num_inputs: int, add_fences: bool): 100 | # initialize fuzzer 101 | fuzzer: Fuzzer = Fuzzer(self.instruction_set_spec, "", test_case_asm) 102 | fuzzer.initialize_modules() 103 | 104 | # Parse the test case and inputs 105 | test_case: TestCase = fuzzer.generator.parse_existing_test_case(test_case_asm) 106 | inputs: List[Input] = fuzzer.input_gen.generate(CONF.input_gen_seed, num_inputs) 107 | 108 | # Load, boost inputs, and trace 109 | fuzzer.model.load_test_case(test_case) 110 | boosted_inputs: List[Input] = fuzzer.boost_inputs(inputs, CONF.model_max_nesting) 111 | 112 | print("Trying to reproduce...") 113 | violations = self._get_all_violations(fuzzer, test_case, boosted_inputs) 114 | if not violations: 115 | print("Could not reproduce the violation. Exiting...") 116 | return 117 | print(f"Found {len(violations)} violations") 118 | 119 | # print("Searching for a minimal input set...") 120 | # min_inputs = self.minimize_inputs(fuzzer, test_case, boosted_inputs, violations) 121 | min_inputs = boosted_inputs 122 | 123 | print("Minimizing the test case...") 124 | min_test_case: TestCase = self.minimize_test_case(fuzzer, test_case, min_inputs) 125 | 126 | if add_fences: 127 | print("Trying to add fences...") 128 | min_test_case = self.add_fences(fuzzer, min_test_case, min_inputs) 129 | 130 | print("Storing the results") 131 | copy(min_test_case.asm_path, outfile) 132 | 133 | def minimize_inputs(self, fuzzer: Fuzzer, test_case: TestCase, inputs: List[Input], 134 | violations: List[EquivalenceClass]) -> List[Input]: 135 | min_inputs: List[Input] = [] 136 | for violation in violations: 137 | for i in range(len(violation)): 138 | measurement = violation.measurements[i] 139 | primer, _ = fuzzer.build_batch_primer(inputs, measurement.input_id, 140 | measurement.htrace, 1) 141 | min_inputs.extend(primer) 142 | 143 | # Make sure these inputs indeed reproduce 144 | violations = self._get_all_violations(fuzzer, test_case, min_inputs) 145 | if not violations or len(min_inputs) > len(inputs): 146 | print("Failed to build a minimal input sequence. Falling back to using all inputs...") 147 | min_inputs = inputs 148 | else: 149 | print(f"Reduced to {len(min_inputs)} inputs") 150 | return min_inputs 151 | 152 | def minimize_test_case(self, fuzzer: Fuzzer, test_case: TestCase, 153 | inputs: List[Input]) -> TestCase: 154 | 155 | def skip_instruction(instructions, i): 156 | return instructions[:i] + instructions[i + 1:] 157 | 158 | return self._probe_test_case(fuzzer, test_case, inputs, skip_instruction) 159 | 160 | def add_fences(self, fuzzer: Fuzzer, test_case: TestCase, inputs: List[Input]) -> TestCase: 161 | 162 | def push_fence(instructions, i): 163 | return instructions[:i] + ["LFENCE\n"] + instructions[i:] 164 | 165 | return self._probe_test_case(fuzzer, test_case, inputs, push_fence) 166 | -------------------------------------------------------------------------------- /src/x86/tests/kernel_module.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | INPUT_SIZE=$((4096 * 3)) 4 | NOP_OPCODE='\x90' 5 | 6 | function setup_suite { 7 | sudo modprobe msr 8 | sudo wrmsr -a 0x1a4 15 9 | } 10 | 11 | @test "x86 executor: Loading a test case" { 12 | echo -n -e $NOP_OPCODE >/sys/x86_executor/test_case 13 | 14 | run bash -c 'echo "1" >/sys/x86_executor/n_inputs' 15 | [ "$status" -eq 0 ] 16 | 17 | printf '%0.s\x01' $(seq 1 $INPUT_SIZE) > tmp.bin 18 | run bash -c 'cat tmp.bin > /sys/x86_executor/inputs' 19 | [ "$status" -eq 0 ] 20 | rm tmp.bin 21 | 22 | run cat /sys/x86_executor/inputs 23 | [ "$status" -eq 0 ] 24 | echo "Output: $output" 25 | [[ "$output" -eq "1" ]] 26 | } 27 | 28 | @test "x86 executor: Printing base addresses" { 29 | run cat /sys/x86_executor/print_sandbox_base 30 | echo "Output: $output" 31 | [[ "$output" != "0" ]] 32 | run cat /sys/x86_executor/print_code_base 33 | echo "Output: $output" 34 | [[ "$output" != "0" ]] 35 | } 36 | 37 | @test "x86 executor: Controlling warmups" { 38 | echo "50" > /sys/x86_executor/warmups 39 | run cat /sys/x86_executor/warmups 40 | [[ "$output" -eq "50" ]] 41 | } 42 | 43 | function load_test_case() { 44 | local test_file=$1 45 | 46 | tmpbin=$(mktemp /tmp/revizor-test.XXXXXX.o) 47 | 48 | as "$test_file" -o "$tmpbin" 49 | strip --remove-section=.note.gnu.property "$tmpbin" 50 | objcopy "$tmpbin" -O binary "$tmpbin" 51 | 52 | cat $tmpbin >/sys/x86_executor/test_case 53 | echo "1" >/sys/x86_executor/n_inputs 54 | printf '%0.s\x01' $(seq 1 $INPUT_SIZE) > /sys/x86_executor/inputs 55 | run cat /sys/x86_executor/inputs 56 | [[ "$output" -eq "1" ]] 57 | 58 | rm "$tmpbin" 59 | } 60 | 61 | @test "x86 executor: Hardware tracing with P+P" { 62 | echo "P+P" > /sys/x86_executor/measurement_mode 63 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 64 | 65 | echo "NOP" > $tmpasm 66 | load_test_case $tmpasm 67 | run cat /sys/x86_executor/trace 68 | echo "Output: $output" 69 | [[ "$output" == *"9223372036854775808,0,0"* ]] 70 | 71 | echo "MOVQ %r14, %rax; add \$512, %rax; movq (%rax), %rax" > $tmpasm 72 | load_test_case $tmpasm 73 | run cat /sys/x86_executor/trace 74 | echo "Output: $output" 75 | [[ "$output" == *"9259400833873739776,0,0"* ]] 76 | 77 | rm "$tmpasm" 78 | } 79 | 80 | @test "x86 executor: Hardware tracing with F+R" { 81 | echo "F+R" > /sys/x86_executor/measurement_mode 82 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 83 | 84 | echo "NOP" > $tmpasm 85 | load_test_case $tmpasm 86 | run cat /sys/x86_executor/trace 87 | echo "Output: $output" 88 | [[ "$output" == *"0,0,0"* ]] 89 | 90 | echo "MOVQ %r14, %rax; add \$512, %rax; movq (%rax), %rax" > $tmpasm 91 | load_test_case $tmpasm 92 | run cat /sys/x86_executor/trace 93 | echo "Output: $output" 94 | [[ "$output" == *"36028797018963968,0,0"* ]] 95 | 96 | rm "$tmpasm" 97 | } 98 | 99 | @test "x86 executor: Hardware tracing with E+R" { 100 | echo "E+R" > /sys/x86_executor/measurement_mode 101 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 102 | 103 | echo "NOP" > $tmpasm 104 | load_test_case $tmpasm 105 | run cat /sys/x86_executor/trace 106 | echo "Output: $output" 107 | [[ "$output" == *"0,0,0"* ]] 108 | 109 | echo "MOVQ %r14, %rax; add \$512, %rax; movq (%rax), %rax" > $tmpasm 110 | load_test_case $tmpasm 111 | run cat /sys/x86_executor/trace 112 | echo "Output: $output" 113 | [[ "$output" == *"36028797018963968,0,0"* ]] 114 | 115 | rm "$tmpasm" 116 | } 117 | 118 | @test "x86 executor: Noise Level" { 119 | # execute one dummy run to set Executor into the default config and to load the test case 120 | nruns=10000 121 | threshold=$((nruns - 2)) 122 | 123 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 124 | tmpbin=$(mktemp /tmp/revizor-test.XXXXXX.o) 125 | tmpinput=$(mktemp /tmp/revizor-test.XXXXXX.bin) 126 | tmpresult=$(mktemp /tmp/revizor-test.XXXXXX.txt) 127 | 128 | echo "NOP" > $tmpasm 129 | as "$tmpasm" -o "$tmpbin" 130 | 131 | strip --remove-section=.note.gnu.property "$tmpbin" 132 | objcopy "$tmpbin" -O binary "$tmpbin" 133 | 134 | dd if=/dev/zero of="$tmpinput" bs=$INPUT_SIZE count=$nruns status=none 135 | 136 | for mode in "P+P" "F+R" "E+R"; do 137 | # echo $mode 138 | echo $mode > /sys/x86_executor/measurement_mode 139 | cat $tmpbin >/sys/x86_executor/test_case 140 | echo "$nruns" >/sys/x86_executor/n_inputs 141 | cat $tmpinput > /sys/x86_executor/inputs 142 | run cat /sys/x86_executor/inputs 143 | [[ "$output" -eq "1" ]] 144 | 145 | echo "" > $tmpresult 146 | 147 | # START=$(date +%s.%N) 148 | while true; do 149 | run cat /sys/x86_executor/trace 150 | [ "$status" -eq 0 ] 151 | echo "$output" >> $tmpresult 152 | if [[ "$output" == *"done"* ]]; then 153 | break 154 | fi 155 | done 156 | # END=$(date +%s.%N) 157 | # echo "$END - $START" | bc 158 | 159 | # cat $tmpresult | awk '/,/{print $1}' | sort | uniq -c | sort -r | awk '//{print $1}' 160 | run bash -c "cat $tmpresult | awk '/,/{print \$1}' | sort | uniq -c | sort -r | awk '//{print \$1}' | head -n1" 161 | [ $output -ge $threshold ] 162 | done 163 | rm $tmpasm 164 | rm "$tmpbin" 165 | rm "$tmpinput" 166 | rm "$tmpresult" 167 | } 168 | 169 | @test "x86 executor: Noisy stores" { 170 | # execute one dummy run to set Executor into the default config and to load the test case 171 | nruns=10000 172 | threshold=$((nruns - 2)) 173 | 174 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 175 | tmpbin=$(mktemp /tmp/revizor-test.XXXXXX.o) 176 | tmpinput=$(mktemp /tmp/revizor-test.XXXXXX.bin) 177 | tmpresult=$(mktemp /tmp/revizor-test.XXXXXX.txt) 178 | 179 | echo "MOVQ %r14, %rax; add \$512, %rax; movq \$128, (%rax)" > $tmpasm 180 | as "$tmpasm" -o "$tmpbin" 181 | 182 | strip --remove-section=.note.gnu.property "$tmpbin" 183 | objcopy "$tmpbin" -O binary "$tmpbin" 184 | 185 | dd if=/dev/zero of="$tmpinput" bs=$INPUT_SIZE count=$nruns status=none 186 | 187 | mode="P+P" 188 | echo $mode > /sys/x86_executor/measurement_mode 189 | cat $tmpbin >/sys/x86_executor/test_case 190 | echo "$nruns" >/sys/x86_executor/n_inputs 191 | cat $tmpinput > /sys/x86_executor/inputs 192 | run cat /sys/x86_executor/inputs 193 | [[ "$output" -eq "1" ]] 194 | 195 | echo "" > $tmpresult 196 | 197 | while true; do 198 | run cat /sys/x86_executor/trace 199 | [ "$status" -eq 0 ] 200 | echo "$output" >> $tmpresult 201 | if [[ "$output" == *"done"* ]]; then 202 | break 203 | fi 204 | done 205 | 206 | run bash -c "cat $tmpresult | awk '/,/{print \$1}' | sort | uniq -c | sort -r | awk '//{print \$1}' | head -n1" 207 | [ $output -ge $threshold ] 208 | 209 | rm $tmpasm 210 | rm "$tmpbin" 211 | rm "$tmpinput" 212 | rm "$tmpresult" 213 | } 214 | 215 | @test "x86 executor: Detection of machine clears" { 216 | echo "P+P" > /sys/x86_executor/measurement_mode 217 | echo "1" > /sys/x86_executor/enable_mds 218 | tmpasm=$(mktemp /tmp/revizor-test.XXXXXX.asm) 219 | 220 | echo "MOVQ %r14, %rax; add \$4096, %rax; movq (%rax), %rax" > $tmpasm 221 | load_test_case $tmpasm 222 | run cat /sys/x86_executor/trace 223 | echo "Output: $output" 224 | [[ "$output" == *",1,0,"* ]] 225 | 226 | rm "$tmpasm" 227 | } -------------------------------------------------------------------------------- /docs/how-revizor-works.md: -------------------------------------------------------------------------------- 1 | # How Revizor works 2 | 3 | Table of Contents: 4 | - [How Revizor works](#how-revizor-works) 5 | - [Revizor in a nutshell](#revizor-in-a-nutshell) 6 | - [Speculation Contracts](#speculation-contracts) 7 | - [Microarchitectural Leakage and Hardware Traces](#microarchitectural-leakage-and-hardware-traces) 8 | - [What's a Speculation Contract?](#whats-a-speculation-contract) 9 | - [Model-based Relational Testing](#model-based-relational-testing) 10 | - [Revizor](#revizor) 11 | 12 | 13 | ## Revizor in a nutshell 14 | 15 | Revizor is a tool for detecting unexpected microarchitectural leakage in CPUs. 16 | Microarchitectural leakage means the information that an attacker could learn by launching a microarchitectural side-channel attack (e.g., [Spectre or Meltdown](https://meltdownattack.com/)). 17 | The *expected* microarchitectural leakage is the leakage that we already know about (i.e., known microarchitectural vulnerabilities). 18 | We describe the expected leakage in a form of a Speculation Contract (see below). 19 | Accordingly, the *unexpected* leakage is any leakage not described by a contract - we call it a *contract violation*. 20 | Revizor's task is to find such violations. 21 | 22 | 23 | ## Speculation Contracts 24 | 25 | Below is a brief intro to Contracts. You can find a more detailed description in the [original paper](https://arxiv.org/abs/2006.03841) and in the Background section of the [Revizor paper](https://arxiv.org/pdf/2105.06872.pdf). 26 | 27 | ### Microarchitectural Leakage and Hardware Traces 28 | 29 | Consider two programs, an attacker and a victim. 30 | The attacker launches a microarchitectural side-channel attack (e.g., a cache side channel) to spy on the victim and learn some of its data. 31 | A *hardware trace* is a sequence of all the observations made through this side-channel after each instruction during the victim's execution. 32 | In other words, hardware trace is the result for a side-channel attack. 33 | 34 | We abstractly represent the hardware trace as the output of a function 35 | 36 | 𝐻𝑇𝑟𝑎𝑐𝑒 =𝐴𝑡𝑡𝑎𝑐𝑘(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎,𝐶𝑡𝑥) 37 | 38 | that takes three input parameters: 39 | (1) the victim program 𝑃𝑟𝑜𝑔; 40 | (2) the input 𝐷𝑎𝑡𝑎 processed by the victim’s program (i.e., the architectural state including registers and main memory); 41 | (3) the microarchitectural context 𝐶𝑡𝑥 in which it executes. 42 | The information exposed by a hardware trace depends on the assumed side-channel and threat model. 43 | 44 | Example: If the threat model includes attacks on a data cache, 𝐻𝑇𝑟𝑎𝑐𝑒 is composed of the cache set indexes used by 𝑃𝑟𝑜𝑔’s loads and stores. 45 | If it includes attacks on an instruction cache, 𝐻𝑇𝑟𝑎𝑐𝑒 contains the addresses of executed instructions. 46 | 47 | A program *leaks* information via side-channels when its hardware traces depend on the inputs (𝐷𝑎𝑡𝑎): 48 | We assume the attacker knows 𝑃𝑟𝑜𝑔 and can manipulate 𝐶𝑡𝑥, hence any difference between the hardware traces implies difference in 𝐷𝑎𝑡𝑎, which effectively exposes information to the attacker. 49 | 50 | ### What's a Speculation Contract? 51 | 52 | A speculation contract specifies the information that can be exposed by a CPU during a program execution under a given threat model. 53 | For each instruction in the CPU ISA (or a subset thereof), a contract describes the information exposed by the instruction’s (observation clause) and the externally-observable speculation that the instruction may trigger (execution clause). 54 | When a contract covers a subset of ISA, the leakage of unspecified instructions is undefined. 55 | 56 | Example: consider the contract summarized in the next table: 57 | 58 | | | Observation Clause | Execution Clause | 59 | | ---------- | ------------------ | ----------------- | 60 | | Load | Expose Address | - | 61 | | Store | Expose Address | - | 62 | | Cond. Jump | - | Mispredict Target | 63 | | Other | - | - | 64 | 65 | We call this contract MEM-COND. 66 | Through the observation clauses of loads and stores, the contract prescribes that addresses of all memory access may be exposed (hence MEM). 67 | The execution clause of conditional branches describes their misprediction, thus the contract prescribes that branch targets may be mispredicted (hence COND). 68 | This way, the contract models a data cache side channel on a CPU with branch prediction. 69 | 70 | A contract trace 𝐶𝑇𝑟𝑎𝑐𝑒 contains the sequence of all the observations the contract allows to be exposed after each instruction during a program execution, including the instructions executed speculatively. 71 | Conversely, the information that is not exposed via 𝐶𝑇𝑟𝑎𝑐𝑒 is supposed to be kept secret. 72 | 73 | We abstractly represent a contract as a function 𝐶𝑜𝑛𝑡𝑟𝑎𝑐𝑡 that maps the program 𝑃𝑟𝑜𝑔 and its input 𝐷𝑎𝑡𝑎 to a contract trace 𝐶𝑇𝑟𝑎𝑐𝑒: 74 | 75 | 𝐶𝑇𝑟𝑎𝑐𝑒 = 𝐶𝑜𝑛𝑡𝑟𝑎𝑐𝑡(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎) 76 | 77 | Example: Consider the following program: 78 | 79 | ```python 80 | z = array1[x] # base of array1 is 0x100 81 | if y < 10: 82 | z = array2[y] # base of array2 is 0x200 83 | ``` 84 | It is executed with an input `data={x=10,y=20}`. 85 | The MEM-COND contract trace is `ctrace=[0x110,0x220]`, representing that the load at line 1 exposes the accessed address during normal execution, and the load at line 3 exposes its address during speculative execution triggered by the branch at line 2. 86 | 87 | A CPU complies with a contract when its hardware traces (collected on the actual CPU) leak at most as much information as the contract traces. 88 | Formally, we require that whenever any two executions of any program have the same contract trace (implying the difference between inputs is not exposed), the respective hardware traces should also match. 89 | 90 | A CPU complies with a 𝐶𝑜𝑛𝑡𝑟𝑎𝑐𝑡 if, for all programs 𝑃𝑟𝑜𝑔, all input pairs (𝐷𝑎𝑡𝑎,𝐷𝑎𝑡𝑎′), and all initial microarchitectural states 𝐶𝑡𝑥: 91 | 92 | 𝐶𝑜𝑛𝑡𝑟𝑎𝑐𝑡(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎) = 𝐶𝑜𝑛𝑡𝑟𝑎𝑐𝑡(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎′) 93 | -> 𝐴𝑡𝑡𝑎𝑐𝑘(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎,𝐶𝑡𝑥) = 𝐴𝑡𝑡𝑎𝑐𝑘(𝑃𝑟𝑜𝑔,𝐷𝑎𝑡𝑎′,𝐶𝑡𝑥) 94 | 95 | Conversely, a CPU violates a contract if there exists a program 𝑃𝑟𝑜𝑔, a microarchitectural state Ctx, and two inputs 𝐷𝑎𝑡𝑎,𝐷𝑎𝑡𝑎′ that agree on their contract traces but disagree on the hardware traces. 96 | We call the tuple (𝑃𝑟𝑜𝑔,𝐶𝑡𝑥,𝐷𝑎𝑡𝑎,𝐷𝑎𝑡𝑎′) a contract counterexample. 97 | The counterexample witnesses that an adversary can learn more information from hardware traces than what the contract specifies. 98 | A counterexample indicates a potential microarchitectural vulnerability that was not accounted for by the contract. 99 | 100 | ## Model-based Relational Testing 101 | 102 | To find contract violations, we use the following approach, which we call Model-based Relational Testing (MRT). 103 | 104 | The next figure show the main components of MRT: 105 | 106 | ![MRT](./diagrams/Arch.png) 107 | 108 | **Test case and input generation**. 109 | We sample the search space of programs, inputs and microarchitectural states to find counterexamples. 110 | The generated instruction sequences (test cases) are comprised of the ISA subset described by the contract. 111 | The test cases and respective inputs to them are generated to achieve high diversity and to increase speculation or leakage potential. 112 | 113 | **Collecting contract traces.** 114 | We implement an executable Model of the contract to allow automatic collection of contract traces for standard binaries. 115 | For this, we modify a functional CPU emulator to implement speculative control flow based on a contract’s execution 116 | clause, and to record traces based on its observation clause. 117 | 118 | **Collecting hardware traces.** 119 | We collect hardware traces by executing the test case on the CPU under test and measuring the observable microarchitectural state changes during the execution according to the threat model. 120 | The executor employs several methods to achieve consistent and repeatable measurements. 121 | 122 | **Relational Analysis.** 123 | Based on the collected contract and hardware traces, we identify contract violations. 124 | Namely, we search for pairs of inputs that match the following: 125 | 126 | ``` 127 | ContractTrace1 == ContractTrace2 128 | and 129 | HardwareTrace1 != HardwareTrace2 130 | ``` 131 | 132 | This requires relational reasoning: 133 | * We partition inputs into groups, which we call input classes. 134 | All inputs within a class have the same contract trace. 135 | Thus, input classes correspond to the equivalence classes of equality on contract traces. 136 | Classes with a single (ineffective) input are discarded. 137 | * For each class, we check if all inputs within a class have the same hardware trace. 138 | 139 | If the check fails on any of the classes, we found a counterexample that witnesses contract violation. 140 | 141 | ## Revizor 142 | 143 | Revizor implements the MRT approach for black-box CPUs. 144 | The implementation details are described in [Revizor Architecture](./architecture.md). -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Fuzzing Configuration Options 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | from typing import List 8 | 9 | 10 | class ConfCls: 11 | config_path: str = "" 12 | # ============================================================================================== 13 | # Fuzzer 14 | no_priming = False 15 | min_primer_size: int = 1 # deprecated? # better leave at 1; otherwise may fail to build primer 16 | max_primer_size: int = 1000 # deprecated? 17 | # ============================================================================================== 18 | # Generator 19 | instruction_set = "x86-64" 20 | generator = "random" 21 | test_case_generator_seed: int = 0 22 | min_bb_per_function = 2 23 | max_bb_per_function = 2 24 | test_case_size = 24 25 | avg_mem_accesses = 12 26 | randomized_mem_alignment: bool = True 27 | avoid_data_dependencies: bool = False 28 | generate_memory_accesses_in_pairs: bool = False 29 | memory_access_zeroed_bits: int = 6 30 | supported_categories = [ 31 | # Base x86 32 | "BASE-BINARY", 33 | "BASE-BITBYTE", 34 | "BASE-CMOV", 35 | "BASE-COND_BR", 36 | "BASE-CONVERT", 37 | "BASE-DATAXFER", 38 | "BASE-FLAGOP", 39 | "BASE-LOGICAL", 40 | "BASE-MISC", 41 | "BASE-NOP", 42 | "BASE-POP", 43 | "BASE-PUSH", 44 | "BASE-SEMAPHORE", 45 | "BASE-SETCC", 46 | "BASE-STRINGOP", 47 | 48 | # "BASE-ROTATE", # Unknown bug in Unicorn - emulated incorrectly 49 | # "BASE-SHIFT", # Unknown bug in Unicorn - emulated incorrectly 50 | 51 | # "BASE-UNCOND_BR", # Not supported: Complex control flow 52 | # "BASE-CALL", # Not supported: Complex control flow 53 | # "BASE-RET", # Not supported: Complex control flow 54 | 55 | # "BASE-SEGOP", # Not supported: System instructions 56 | # "BASE-INTERRUPT", # Not supported: System instructions 57 | # "BASE-IO", # Not supported: System instructions 58 | # "BASE-IOSTRINGOP", # Not supported: System instructions 59 | # "BASE-SYSCALL", # Not supported: System instructions 60 | # "BASE-SYSRET", # Not supported: System instructions 61 | # "BASE-SYSTEM", # Not supported: System instructions 62 | 63 | # Extensions 64 | "SSE-MISC", # SFENCE 65 | "SSE2-MISC", # LFENCE, MFENCE 66 | "CLFLUSHOPT-CLFLUSHOPT", 67 | "CLFSH-MISC", 68 | # "BMI1", 69 | ] 70 | instruction_blocklist = [ 71 | # Hard to fix: 72 | # - STI - enables interrupts, thus corrupting the measurements; CLI - just in case 73 | "STI", "CLI", 74 | # - CMPXCHG8B - Unicorn doesn't execute the mem. access hook 75 | # bug: https://github.com/unicorn-engine/unicorn/issues/990 76 | "CMPXCHG8B", "LOCK CMPXCHG8B", 77 | # - Undefined instructions are, well, undefined 78 | "UD", "UD2", 79 | # - Incorrect emulation 80 | "CPUID", 81 | # - Requires support of segment registers 82 | "XLAT", "XLATB", 83 | # - Requires special instrumentation to avoid #DE faults 84 | "IDIV", "REX IDIV", 85 | # - Requires complex instrumentation 86 | "ENTERW", "ENTER", "LEAVEW", "LEAVE", 87 | 88 | # Stringops - under construction 89 | "CMPSB", "CMPSD", "CMPSW", 90 | "MOVSB", "MOVSD", "MOVSW", 91 | 92 | "REPE LODSB", "REPE LODSD", "REPE LODSW", 93 | "REPE SCASB", "REPE SCASD", "REPE SCASW", 94 | "REPE STOSB", "REPE STOSD", "REPE STOSW", 95 | "REPE CMPSB", "REPE CMPSD", "REPE CMPSW", 96 | "REPE MOVSB", "REPE MOVSD", "REPE MOVSW", 97 | 98 | "REPNE LODSB", "REPNE LODSD", "REPNE LODSW", 99 | "REPNE SCASB", "REPNE SCASD", "REPNE SCASW", 100 | "REPNE STOSB", "REPNE STOSD", "REPNE STOSW", 101 | "REPNE CMPSB", "REPNE CMPSD", "REPNE CMPSW", 102 | "REPNE MOVSB", "REPNE MOVSD", "REPNE MOVSW", 103 | # - not supported 104 | "LFENCE", "MFENCE", "SFENCE", "CLFLUSH", "CLFLUSHOPT" 105 | ] # yapf: disable 106 | extended_instruction_blocklist: List[str] = [] 107 | # x86 executor internally uses R15, R14, RSP, RBP and, thus, they are excluded 108 | # segment registers are also excluded as we don't support their handling so far 109 | # same for CR* and DR* 110 | gpr_blocklist = [ 111 | # free - rax, rbx, rcx, rdx, rdi, rsi 112 | 'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'RSP', 'RBP', 113 | 'R8D', 'R9D', 'R10D', 'R11D', 'R12D', 'R13D', 'R14D', 'R15D', 'ESP', 'EBP', 114 | 'R8W', 'R9W', 'R10W', 'R11W', 'R12W', 'R13W', 'R14W', 'R15W', 'SP', 'BP', 115 | 'R8B', 'R9B', 'R10B', 'R11B', 'R12B', 'R13B', 'R14B', 'R15B', 'SPL', 'BPL', 116 | 'ES', 'CS', 'SS', 'DS', 'FS', 'GS', 117 | 'CR0', 'CR2', 'CR3', 'CR4', 'CR8', 118 | 'DR0', 'DR1', 'DR2', 'DR3', 'DR4', 'DR5', 'DR6', 'DR7' 119 | ] # yapf: disable 120 | _no_generation: bool = False 121 | # ============================================================================================== 122 | # Input Generator 123 | input_generator: str = 'random' 124 | input_gen_seed: int = 10 # zero is a reserved value, do not use it 125 | input_gen_entropy_bits: int = 3 126 | input_main_region_size: int = 4096 127 | input_assist_region_size: int = 4096 128 | input_register_region_size: int = 64 129 | inputs_per_class: int = 2 130 | # ============================================================================================== 131 | # Model 132 | model: str = 'x86-unicorn' 133 | contract_execution_clause: List[str] = ["seq"] # options: "seq", "cond", "bpas" 134 | contract_observation_clause: str = 'ct' 135 | model_max_nesting: int = 5 136 | model_max_spec_window: int = 250 137 | # ============================================================================================== 138 | # Executor 139 | executor: str = 'x86-intel' 140 | executor_mode: str = 'P+P' 141 | executor_warmups: int = 50 142 | executor_repetitions: int = 20 143 | executor_max_outliers: int = 2 144 | executor_taskset: int = 0 145 | enable_ssbp_patch: bool = True 146 | enable_pre_run_flush: bool = True 147 | enable_assist_page: bool = False 148 | # ============================================================================================== 149 | # Analyser 150 | analyser: str = 'equivalence-classes' 151 | analyser_permit_subsets: bool = True 152 | # ============================================================================================== 153 | # Coverage 154 | coverage_type: str = 'none' 155 | feedback_driven_generator: bool = False # temporary unused 156 | # ============================================================================================== 157 | # Output 158 | multiline_output: bool = False 159 | logging_modes: List[str] = ["info", "stat"] 160 | 161 | def set(self, name, value): 162 | options = { 163 | 'instruction_set': ["x86-64"], 164 | 'generator': ["random"], 165 | 'input_generator': ["random"], 166 | 'model': ['x86-unicorn'], 167 | 'executor': ['x86-intel'], 168 | 'executor_mode': ['P+P', 'F+R', 'E+R'], 169 | 'contract_observation_clause': [ 170 | 'l1d', 'memory', 'ct', 'pc', 'ct-nonspecstore', 'ctr', 'arch' 171 | ], 172 | 'coverage_type': ['dependent-pairs', 'none'], 173 | } 174 | 175 | if name[0] == "_": 176 | ConfigException(f"Attempting to set an internal configuration variable {name}.") 177 | if getattr(self, name, None) is None: 178 | ConfigException(f"Unknown configuration variable {name}.\n" 179 | f"It's likely a typo in the configuration file.") 180 | if type(self.__getattribute__(name)) != type(value): 181 | ConfigException(f"Wrong type of the configuration variable {name}.\n" 182 | f"It's likely a typo in the configuration file.") 183 | 184 | # value checks 185 | if options.get(name, '') != '' and value not in options[name]: 186 | ConfigException(f"Unknown value '{value}' of configuration variable '{name}'") 187 | if (self.input_main_region_size % 4096 != 0) or \ 188 | (self.input_assist_region_size % 4096 != 0): 189 | ConfigException("Inputs must be page-aligned") 190 | 191 | # special handling 192 | if name == "extended_instruction_blocklist": 193 | self.instruction_blocklist.extend(value) 194 | 195 | self.__setattr__(name, value) 196 | 197 | def sanity_check(self): 198 | if self.executor_max_outliers > 20: 199 | print(f"WARNING: Configuration: Are you sure you want to" 200 | f" ignore {self.executor_max_outliers} outliers?") 201 | if self.coverage_type == "none": 202 | self.feedback_driven_generator = False 203 | 204 | 205 | CONF = ConfCls() 206 | 207 | 208 | class ConfigException(SystemExit): 209 | pass 210 | -------------------------------------------------------------------------------- /src/x86/executor/measurement.c: -------------------------------------------------------------------------------- 1 | /// File: 2 | /// - Test case execution 3 | /// - Ensuring an isolated environment 4 | /// 5 | // Copyright (C) Microsoft Corporation 6 | // SPDX-License-Identifier: MIT 7 | 8 | #include 9 | #include 10 | #include <../arch/x86/include/asm/fpu/api.h> 11 | #include <../arch/x86/include/asm/pgtable.h> 12 | #include <../arch/x86/include/asm/tlbflush.h> 13 | 14 | #include "main.h" 15 | 16 | struct pfc_config 17 | { 18 | unsigned long evt_num; 19 | unsigned long umask; 20 | unsigned long cmask; 21 | unsigned int any; 22 | unsigned int edge; 23 | unsigned int inv; 24 | }; 25 | 26 | unsigned long faulty_page_addr; 27 | pte_t faulty_page_pte; 28 | pte_t *faulty_page_ptep; 29 | 30 | int config_pfc(unsigned int id, char *pfc_code, unsigned int usr, unsigned int os); 31 | pte_t *get_pte(unsigned long address); 32 | 33 | inline void wrmsr64(unsigned int msr, uint64_t value) 34 | { 35 | native_write_msr(msr, (uint32_t)value, (uint32_t)(value >> 32)); 36 | } 37 | 38 | // ================================================================================================= 39 | // Measurement 40 | // ================================================================================================= 41 | static inline int pre_measurement_setup(void) 42 | { 43 | // on some microarchitectures (e.g., Broadwell), some events 44 | // (e.g., L1 misses) are not counted properly if only the OS field is set 45 | int err = 0; 46 | err |= config_pfc(0, "D1.01", 1, 1); // L1 hits - for htrace collection 47 | err |= config_pfc(1, "C3.01.CMSK=1.EDG", 1, 1); // machine clears - fuzzing feedback 48 | err |= config_pfc(2, "C5.00", 1, 1); // mispredicted branches - fuzzing feedback 49 | err |= config_pfc(3, "C1.07", 1, 1); // unused 50 | if (err) 51 | return err; 52 | 53 | wrmsr64(MSR_IA32_SPEC_CTRL, ssbp_patch_control); 54 | 55 | faulty_page_addr = (unsigned long)&sandbox->faulty_region[0]; 56 | faulty_page_ptep = get_pte(faulty_page_addr); 57 | if (faulty_page_ptep == NULL) 58 | { 59 | printk(KERN_ERR "x86_executor: Couldn't get the faulty page PTE entry"); 60 | return -1; 61 | } 62 | return 0; 63 | } 64 | 65 | void run_experiment(long rounds) 66 | { 67 | get_cpu(); 68 | unsigned long flags; 69 | raw_local_irq_save(flags); 70 | 71 | for (long i = -uarch_reset_rounds; i < rounds; i++) 72 | { 73 | // ignore "warm-up" runs (i<0)uarch_reset_rounds 74 | long i_ = (i < 0) ? 0 : i; 75 | uint64_t *current_input = &inputs[i_ * INPUT_SIZE / 8]; 76 | 77 | // Initialize memory: 78 | // NOTE: memset is not used intentionally! somehow, it messes up with P+P measurements 79 | 80 | // - eviction region is initialized with zeroes 81 | for (int j = 0; j < EVICT_REGION_SIZE / 8; j += 1) { 82 | ((uint64_t *) sandbox->eviction_region)[j] = 0; 83 | } 84 | 85 | // - overflows are initialized with zeroes 86 | memset(&sandbox->lower_overflow[0], 0, OVERFLOW_REGION_SIZE * sizeof(char)); 87 | for (int j = 0; j < OVERFLOW_REGION_SIZE / 8; j += 1) { 88 | // ((uint64_t *) sandbox->lower_overflow)[j] = 0; 89 | ((uint64_t *) sandbox->upper_overflow)[j] = 0; 90 | } 91 | 92 | // - sandbox: main and faulty regions 93 | uint64_t *main_page_values = ¤t_input[0]; 94 | uint64_t *main_base = (uint64_t *)&sandbox->main_region[0]; 95 | for (int j = 0; j < MAIN_REGION_SIZE / 8; j += 1) 96 | { 97 | ((uint64_t *)main_base)[j] = main_page_values[j]; 98 | } 99 | 100 | uint64_t *faulty_page_values = ¤t_input[MAIN_REGION_SIZE / 8]; 101 | uint64_t *faulty_base = (uint64_t *)&sandbox->faulty_region[0]; 102 | for (int j = 0; j < FAULTY_REGION_SIZE / 8; j += 1) 103 | { 104 | ((uint64_t *)faulty_base)[j] = faulty_page_values[j]; 105 | } 106 | 107 | // Initial register values (the registers will be set to these values in template.c) 108 | uint64_t *register_values = ¤t_input[(MAIN_REGION_SIZE + FAULTY_REGION_SIZE) / 8]; 109 | uint64_t *register_initialization_base = (uint64_t *)&sandbox->upper_overflow[0]; 110 | 111 | // - RAX ... RDI 112 | for (int j = 0; j < 6; j += 1) 113 | { 114 | ((uint64_t *)register_initialization_base)[j] = register_values[j]; 115 | } 116 | 117 | // - flags 118 | uint64_t masked_flags = (register_values[6] & 2263) | 2; 119 | ((uint64_t *)register_initialization_base)[6] = masked_flags; 120 | 121 | // - RSP and RBP 122 | ((uint64_t *)register_initialization_base)[7] = (uint64_t)stack_base; 123 | 124 | // flush some of the uarch state 125 | if (pre_run_flush == 1) 126 | { 127 | static const u16 ds = __KERNEL_DS; 128 | asm volatile("verw %[ds]" 129 | : 130 | : [ds] "m"(ds) 131 | : "cc"); 132 | wrmsr64(MSR_IA32_FLUSH_CMD, L1D_FLUSH); 133 | } 134 | 135 | // clear the ACCESSED bit and flush the corresponding TLB entry 136 | if (enable_faulty_page) 137 | { 138 | faulty_page_pte.pte = faulty_page_ptep->pte & ~_PAGE_ACCESSED; 139 | set_pte_at(current->mm, faulty_page_addr, faulty_page_ptep, faulty_page_pte); 140 | asm volatile("clflush (%0)\nlfence\n" ::"r"(faulty_page_addr) 141 | : "memory"); 142 | asm volatile("invlpg (%0)" ::"r"(faulty_page_addr) 143 | : "memory"); 144 | } 145 | 146 | // execute 147 | ((void (*)(char *))measurement_code)(&sandbox->main_region[0]); 148 | 149 | // store the measurement results 150 | measurement_t result = sandbox->latest_measurement; 151 | // printk(KERN_ERR "x86_executor: measurement %llu\n", result.htrace[0]); 152 | measurements[i_].htrace[0] = result.htrace[0]; 153 | measurements[i_].pfc[0] = result.pfc[0]; 154 | measurements[i_].pfc[1] = result.pfc[1]; 155 | measurements[i_].pfc[2] = result.pfc[2]; 156 | } 157 | 158 | raw_local_irq_restore(flags); 159 | put_cpu(); 160 | } 161 | 162 | int trace_test_case(void) 163 | { 164 | // Prepare for the experiment: 165 | // 1. Ensure that all necessary objects are allocated 166 | if (!measurements) 167 | { 168 | printk(KERN_ERR "Did not allocate memory for measurements\n"); 169 | return -ENOMEM; 170 | } 171 | if (!measurement_code) 172 | return -1; 173 | if (!inputs) 174 | { 175 | printk(KERN_ERR "Did not allocate memory for inputs\n"); 176 | return -ENOMEM; 177 | } 178 | 179 | // 2. Enable FPU - just in case, we might use it within the test case 180 | kernel_fpu_begin(); 181 | 182 | // 3. Run the measurement 183 | if (pre_measurement_setup()) 184 | return -1; 185 | run_experiment((long)n_inputs); 186 | 187 | kernel_fpu_end(); 188 | return 0; 189 | } 190 | 191 | // ================================================================================================= 192 | // Helper Functions 193 | // ================================================================================================= 194 | 195 | /// Clears the programmable performance counters and writes the 196 | /// configurations to the corresponding MSRs. 197 | /// 198 | int config_pfc(unsigned int id, char *pfc_code_org, unsigned int usr, unsigned int os) 199 | { 200 | // Parse the PFC code name 201 | struct pfc_config config = {0}; 202 | 203 | char pfc_code[50]; 204 | strcpy(pfc_code, pfc_code_org); 205 | char *pfc_code_p = pfc_code; 206 | 207 | int err = 0; 208 | char *evt_num = strsep(&pfc_code_p, "."); 209 | err |= kstrtoul(evt_num, 16, &(config.evt_num)); 210 | 211 | char *umask = strsep(&pfc_code_p, "."); 212 | err |= kstrtoul(umask, 16, &(config.umask)); 213 | 214 | char *ce; 215 | while ((ce = strsep(&pfc_code_p, ".")) != NULL) 216 | { 217 | if (!strcmp(ce, "Any")) 218 | { 219 | config.any = 1; 220 | } 221 | else if (!strcmp(ce, "EDG")) 222 | { 223 | config.edge = 1; 224 | } 225 | else if (!strcmp(ce, "INV")) 226 | { 227 | config.inv = 1; 228 | } 229 | else if (!strncmp(ce, "CMSK=", 5)) 230 | { 231 | err |= kstrtoul(ce + 5, 0, &(config.cmask)); 232 | } 233 | } 234 | 235 | if (err) 236 | return err; 237 | 238 | // Configure the counter 239 | uint64_t global_ctrl = native_read_msr(0x38F); 240 | global_ctrl |= ((uint64_t)7 << 32) | 15; 241 | wrmsr64(0x38F, global_ctrl); 242 | 243 | uint64_t perfevtselx = native_read_msr(0x186 + id); 244 | 245 | // disable the counter 246 | perfevtselx &= ~(((uint64_t)1 << 32) - 1); 247 | wrmsr64(0x186 + id, perfevtselx); 248 | 249 | // clear 250 | wrmsr64(0x0C1 + id, 0); 251 | 252 | perfevtselx |= ((config.cmask & 0xFF) << 24); 253 | perfevtselx |= (config.inv << 23); 254 | perfevtselx |= (1ULL << 22); 255 | perfevtselx |= (config.any << 21); 256 | perfevtselx |= (config.edge << 18); 257 | perfevtselx |= (os << 17); 258 | perfevtselx |= (usr << 16); 259 | perfevtselx |= ((config.umask & 0xFF) << 8); 260 | perfevtselx |= (config.evt_num & 0xFF); 261 | wrmsr64(0x186 + id, perfevtselx); 262 | return 0; 263 | } 264 | 265 | pte_t *get_pte(unsigned long address) 266 | { 267 | pgd_t *pgd; 268 | p4d_t *p4d; 269 | pud_t *pud; 270 | pmd_t *pmd; 271 | pte_t *pte; 272 | 273 | /* Make sure we are in vmalloc area: */ 274 | if (!(address >= VMALLOC_START && address < VMALLOC_END)) 275 | return NULL; 276 | 277 | pgd = pgd_offset(current->mm, address); 278 | if (pgd_none(*pgd)) 279 | return NULL; 280 | 281 | p4d = p4d_offset(pgd, address); 282 | pud = pud_offset(p4d, address); 283 | if (pud_none(*pud)) 284 | return NULL; 285 | 286 | pmd = pmd_offset(pud, address); 287 | if (pmd_none(*pmd)) 288 | return NULL; 289 | 290 | pte = pte_offset_kernel(pmd, address); 291 | if (!pte_present(*pte)) 292 | return NULL; 293 | 294 | return pte; 295 | } -------------------------------------------------------------------------------- /src/x86/isa_spec/get_spec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Copyright (C) Microsoft Corporation 4 | SPDX-License-Identifier: MIT 5 | """ 6 | 7 | import json 8 | import subprocess 9 | from argparse import ArgumentParser 10 | from typing import List 11 | from xml.etree import ElementTree as ET 12 | 13 | 14 | class OperandSpec: 15 | values: List[str] 16 | type_: str 17 | width: int 18 | comment: str 19 | src: bool = False 20 | dest: bool = False 21 | magic: bool = False 22 | 23 | def to_json(self) -> str: 24 | return json.dumps(self, default=vars) 25 | 26 | 27 | class InstructionSpec: 28 | name: str 29 | category: str = "" 30 | control_flow: bool = False 31 | operands: List[OperandSpec] 32 | implicit_operands: List[OperandSpec] 33 | 34 | def __init__(self) -> None: 35 | self.operands = [] 36 | self.implicit_operands = [] 37 | 38 | def __str__(self) -> str: 39 | return f"{self.name} {self.control_flow} {self.category} " \ 40 | f"{len(self.operands)} {len(self.implicit_operands)}" 41 | 42 | def to_json(self) -> str: 43 | s = "{" 44 | s += f'"name": "{self.name}", "category": "{self.category}", ' 45 | s += f'"control_flow": {str(self.control_flow).lower()},\n' 46 | s += ' "operands": [\n ' 47 | s += ',\n '.join([o.to_json() for o in self.operands]) 48 | s += '\n ],\n' 49 | if self.implicit_operands: 50 | s += ' "implicit_operands": [\n ' 51 | s += ',\n '.join([o.to_json() for o in self.implicit_operands]) 52 | s += '\n ]' 53 | else: 54 | s += ' "implicit_operands": []' 55 | s += "\n}" 56 | return s 57 | 58 | 59 | class ParseFailed(Exception): 60 | pass 61 | 62 | 63 | class X86Transformer: 64 | tree: ET.Element 65 | instructions: List[InstructionSpec] 66 | current_spec: InstructionSpec 67 | reg_sizes = { 68 | "RAX": 64, 69 | "RBX": 64, 70 | "RCX": 64, 71 | "RDX": 64, 72 | "EAX": 32, 73 | "EBX": 32, 74 | "ECX": 32, 75 | "EDX": 32, 76 | "AX": 16, 77 | "DX": 16, 78 | "AL": 8, 79 | "AH": 8, 80 | "TMM0": 0, 81 | "MXCSR": 32, 82 | 'ES': 16, 83 | 'SS': 16, 84 | 'DS': 16, 85 | 'FS': 16, 86 | 'GS': 16, 87 | } 88 | 89 | def __init__(self) -> None: 90 | self.instructions = [] 91 | 92 | def load_files(self, filename: str): 93 | parser = ET.ElementTree() 94 | tree = parser.parse(filename) 95 | if not tree: 96 | print("No input. Exiting") 97 | exit(1) 98 | self.tree = tree 99 | 100 | def parse_tree(self, extensions: List[str]): 101 | for instruction_node in self.tree.iter('instruction'): 102 | if instruction_node.attrib.get('sae', '') == '1' or \ 103 | instruction_node.attrib.get('roundc', '') == '1' or \ 104 | instruction_node.attrib.get('zeroing', '') == '1': 105 | continue 106 | 107 | if extensions and instruction_node.attrib['extension'] not in extensions: 108 | continue 109 | 110 | self.instruction = InstructionSpec() 111 | self.instruction.category = instruction_node.attrib['extension'] \ 112 | + "-" \ 113 | + instruction_node.attrib['category'] 114 | 115 | # clean up the name 116 | name = instruction_node.attrib['asm'] 117 | name = name.removeprefix("{load} ") 118 | name = name.removeprefix("{store} ") 119 | name = name.removeprefix("{disp32} ") 120 | self.instruction.name = name 121 | 122 | try: 123 | for op_node in instruction_node.iter('operand'): 124 | op_type = op_node.attrib['type'] 125 | if op_type == 'reg': 126 | parsed_op = self.parse_reg_operand(op_node) 127 | if op_node.text == "RIP": 128 | self.instruction.control_flow = True 129 | elif op_type == 'mem': 130 | parsed_op = self.parse_mem_operand(op_node) 131 | elif op_type == 'agen': 132 | op_node.text = instruction_node.attrib['agen'] 133 | parsed_op = self.parse_agen_operand(op_node) 134 | elif op_type == 'imm': 135 | parsed_op = self.parse_imm_operand(op_node) 136 | elif op_type == 'relbr': 137 | parsed_op = self.parse_label_operand(op_node) 138 | self.instruction.control_flow = True 139 | elif op_type == 'flags': 140 | parsed_op = self.parse_flags_operand(op_node) 141 | else: 142 | raise Exception("Unknown operand type " + op_type) 143 | 144 | if op_node.attrib.get('implicit', '0') == '1': 145 | parsed_op.magic = True 146 | 147 | if op_node.attrib.get('suppressed', '0') == '1': 148 | self.instruction.implicit_operands.append(parsed_op) 149 | else: 150 | self.instruction.operands.append(parsed_op) 151 | 152 | except ParseFailed: 153 | continue 154 | 155 | self.instructions.append(self.instruction) 156 | 157 | def save(self, filename: str): 158 | json_str = "[\n" + ",\n".join([i.to_json() for i in self.instructions]) + "\n]" 159 | # print(json_str) 160 | with open(filename, "w+") as f: 161 | f.write(json_str) 162 | 163 | def parse_reg_operand(self, op): 164 | spec = OperandSpec() 165 | spec.type_ = "REG" 166 | spec.values = op.text.split(',') 167 | spec.src = True if op.attrib.get('r', "0") == "1" else False 168 | spec.dest = True if op.attrib.get('w', "0") == "1" else False 169 | spec.width = int(op.attrib.get('width', 0)) 170 | if spec.width == 0: 171 | if spec.values[0] in self.reg_sizes: 172 | spec.width = self.reg_sizes[spec.values[0]] 173 | else: 174 | raise ParseFailed() 175 | return spec 176 | 177 | @staticmethod 178 | def parse_mem_operand(op): 179 | # asserts are for unsupported instructions 180 | if op.attrib.get('VSIB', '0') != '0': 181 | raise ParseFailed() 182 | # assert op.attrib.get('VSIB', '0') == '0' # asm += '[' + op.attrib.get('VSIB') + '0]' 183 | if op.attrib.get('memory-suffix', '') != '': 184 | raise ParseFailed() 185 | 186 | choices = [] 187 | if op.attrib.get('base', ''): 188 | choices = [op.attrib.get('base', '')] 189 | 190 | spec = OperandSpec() 191 | spec.type_ = "MEM" 192 | spec.values = choices 193 | spec.src = True if op.attrib.get('r', "0") == "1" else False 194 | spec.dest = True if op.attrib.get('w', "0") == "1" else False 195 | spec.width = int(op.attrib.get('width')) 196 | return spec 197 | 198 | @staticmethod 199 | def parse_agen_operand(op): 200 | spec = OperandSpec() 201 | spec.type_ = "AGEN" 202 | spec.values = [] 203 | spec.src = True 204 | spec.dest = False 205 | spec.width = 64 206 | return spec 207 | 208 | @staticmethod 209 | def parse_imm_operand(op): 210 | spec = OperandSpec() 211 | spec.type_ = "IMM" 212 | if op.attrib.get('implicit', '0') == '1': 213 | spec.values = [op.text] 214 | else: 215 | spec.values = [] 216 | spec.src = True 217 | spec.dest = False 218 | spec.width = int(op.attrib.get('width')) 219 | return spec 220 | 221 | @staticmethod 222 | def parse_label_operand(_): 223 | spec = OperandSpec() 224 | spec.type_ = "LABEL" 225 | spec.values = [] 226 | spec.src = True 227 | spec.dest = False 228 | spec.width = 0 229 | return spec 230 | 231 | @staticmethod 232 | def parse_flags_operand(op): 233 | spec = OperandSpec() 234 | spec.type_ = "FLAGS" 235 | spec.values = [ 236 | op.attrib.get("flag_CF", ""), 237 | op.attrib.get("flag_PF", ""), 238 | op.attrib.get("flag_AF", ""), 239 | op.attrib.get("flag_ZF", ""), 240 | op.attrib.get("flag_SF", ""), 241 | op.attrib.get("flag_TF", ""), 242 | op.attrib.get("flag_IF", ""), 243 | op.attrib.get("flag_DF", ""), 244 | op.attrib.get("flag_OF", ""), 245 | ] 246 | spec.src = False 247 | spec.dest = False 248 | spec.width = 0 249 | return spec 250 | 251 | def add_missing(self, extensions): 252 | """ adds the instructions specs that are missing from the XML file we use """ 253 | if not extensions or "CLFSH" in extensions: 254 | for width in [8, 16, 32, 64]: 255 | inst = InstructionSpec() 256 | inst.name = "CLFLUSH" 257 | inst.category = "CLFSH-MISC" 258 | inst.control_flow = False 259 | op = OperandSpec() 260 | op.type_ = "MEM" 261 | op.values = [] 262 | op.src = True 263 | op.dest = False 264 | op.width = width 265 | inst.operands = [op] 266 | self.instructions.append(inst) 267 | 268 | if not extensions or "CLFLUSHOPT" in extensions: 269 | for width in [8, 16, 32, 64]: 270 | inst = InstructionSpec() 271 | inst.name = "CLFLUSHOPT" 272 | inst.category = "CLFLUSHOPT-CLFLUSHOPT" 273 | inst.control_flow = False 274 | op = OperandSpec() 275 | op.type_ = "MEM" 276 | op.values = [] 277 | op.src = True 278 | op.dest = False 279 | op.width = width 280 | inst.operands = [op] 281 | self.instructions.append(inst) 282 | 283 | 284 | def main(): 285 | parser = ArgumentParser(description='', add_help=False) 286 | parser.add_argument( 287 | "--extensions", 288 | nargs="*", 289 | default=[] 290 | ) 291 | args = parser.parse_args() 292 | 293 | subprocess.run("wget " 294 | "https://uops.info/instructions_Jan2022.xml", shell=True, check=True) 295 | 296 | try: 297 | transformer = X86Transformer() 298 | transformer.load_files("instructions_Jan2022.xml") 299 | transformer.parse_tree(args.extensions) 300 | transformer.add_missing(args.extensions) 301 | print(f"Produced base.json with {len(transformer.instructions)} instructions") 302 | transformer.save("base.json") 303 | finally: 304 | subprocess.run("rm instructions_Jan2022.xml", shell=True, check=True) 305 | 306 | 307 | if __name__ == "__main__": 308 | main() 309 | -------------------------------------------------------------------------------- /src/service.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Global classes that provide service to all Revizor modules 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | 8 | from datetime import datetime 9 | 10 | from interfaces import EquivalenceClass 11 | from config import CONF 12 | from typing import NoReturn 13 | 14 | MASK_64BIT = pow(2, 64) 15 | POW2_64 = pow(2, 64) 16 | TWOS_COMPLEMENT_MASK_64 = pow(2, 64) - 1 17 | 18 | 19 | class StatisticsCls: 20 | test_cases = 0 21 | num_inputs = 0 22 | eff_classes = 0 23 | single_entry_classes = 0 24 | required_priming = 0 25 | priming_errors = 0 26 | violations = 0 27 | coverage = 0 28 | coverage_longest_uncovered = 0 29 | fully_covered: int = 0 30 | 31 | def __str__(self): 32 | total_clss = self.eff_classes + self.single_entry_classes 33 | effectiveness = self.eff_classes / total_clss if total_clss else 0 34 | total_clss_per_test_case = total_clss / self.test_cases if self.test_cases else 0 35 | effective_clss = self.eff_classes / self.test_cases if self.test_cases else 0 36 | 37 | s = "\n================================ Statistics ===================================\n" 38 | s += f"Test Cases: {self.test_cases}\n" 39 | s += f"Inputs per test case: {self.num_inputs / self.test_cases:.1f}\n" 40 | s += "Coverage:\n" 41 | s += f" Patterns: {self.coverage}\n" 42 | s += f" Fully covered: {self.fully_covered}\n" 43 | s += f" Longest uncovered: {self.coverage_longest_uncovered}\n" 44 | s += f" Effectiveness: {effectiveness:.1f}\n" 45 | s += "Effectiveness: \n" 46 | s += f" Total Cls: {total_clss_per_test_case:.1f}\n" 47 | s += f" Effective Cls: {effective_clss:.1f}\n" 48 | s += f"Required priming: {self.required_priming}\n" 49 | s += f"Priming errors: {self.priming_errors}\n" 50 | s += f"Violations: {self.violations}\n" 51 | return s 52 | 53 | def get_brief(self): 54 | if self.test_cases == 0: 55 | return "" 56 | else: 57 | s = f"EfCl:{self.eff_classes / self.test_cases:.1f}, " 58 | s += f"AlCl:{(self.eff_classes + self.single_entry_classes) / self.test_cases:.1f}, " 59 | s += f"In:{self.num_inputs / self.test_cases:.1f}, " 60 | s += f"Cov:{self.coverage}, " 61 | s += f"Prim:{self.required_priming}, " \ 62 | f"PErr:{self.priming_errors}, " \ 63 | f"Viol:{self.violations}, " 64 | return s 65 | 66 | 67 | STAT = StatisticsCls() 68 | 69 | 70 | class Logger: 71 | """ 72 | A global object responsible for printing stuff. 73 | 74 | Has the following levels of logging: 75 | - Error: Critical error. Prints a message and exits 76 | - Warning: Non-critical error. Always printed, but does not exit 77 | - Info: Useful info. Printed only if enabled in CONF.logging_modes 78 | - Debug: Detailed info. Printed if both enabled in CONF.logging_modes and if __debug__ is set. 79 | Enabled separately for each module. 80 | - Trace: Same as debug, but for the cases when the amount of printed info is huge 81 | """ 82 | 83 | one_percent_progress: float = 0.0 84 | progress: float = 0.0 85 | progress_percent: int = 0 86 | msg: str = "" 87 | line_ending: str = "" 88 | redraw_mode: bool = True 89 | 90 | # info modes 91 | info_enabled: bool = False 92 | stat_enabled: bool = False 93 | 94 | # debugging modes 95 | fuzzer_debug: bool = False 96 | fuzzer_trace: bool = False 97 | model_debug: bool = False 98 | coverage_debug: bool = False 99 | 100 | def __init__(self) -> None: 101 | pass 102 | 103 | def set_logging_modes(self): 104 | mode_list = CONF.logging_modes 105 | if "info" in mode_list: 106 | self.info_enabled = True 107 | if "stat" in mode_list: 108 | self.stat_enabled = True 109 | if "fuzzer_debug" in mode_list: 110 | self.fuzzer_debug = True 111 | if "fuzzer_trace" in mode_list: 112 | self.fuzzer_trace = True 113 | if "model_debug" in mode_list: 114 | self.model_debug = True 115 | if "coverage_debug" in mode_list: 116 | self.coverage_debug = True 117 | 118 | if not __debug__: 119 | if self.fuzzer_debug or self.model_debug or self.coverage_debug or self.fuzzer_trace: 120 | self.waring("", "Debugging mode was not enabled! Remove '-O' from python arguments") 121 | 122 | def error(self, msg) -> NoReturn: 123 | if self.redraw_mode: 124 | print("") 125 | print(f"ERROR: {msg}") 126 | exit(1) 127 | 128 | def waring(self, src, msg) -> None: 129 | if self.redraw_mode: 130 | print("") 131 | print(f"WARNING: [{src}] {msg}") 132 | 133 | def info(self, src, msg, end="\n") -> None: 134 | if self.info_enabled: 135 | if self.redraw_mode: 136 | print("") 137 | print(f"INFO: [{src}] {msg}", end=end, flush=True) 138 | 139 | # ============================================================================================== 140 | # Fuzzer 141 | def dbg_fuzzer(self, msg) -> None: 142 | if __debug__: 143 | if self.fuzzer_debug: 144 | print(f"DBG: [fuzzer] {msg}") 145 | 146 | def fuzzer_start(self, iterations: int, start_time): 147 | if self.info_enabled: 148 | self.one_percent_progress = iterations / 100 149 | self.progress = 0 150 | self.progress_percent = 0 151 | self.msg = "" 152 | self.line_ending = '\n' if CONF.multiline_output else '' 153 | self.redraw_mode = False if CONF.multiline_output else True 154 | self.start_time = start_time 155 | self.info("fuzzer", start_time.strftime('Starting at %H:%M:%S')) 156 | 157 | def fuzzer_start_round(self, round_id): 158 | if __debug__ and round_id and round_id % 1000 == 0: 159 | self.dbg_fuzzer( 160 | f"Current duration: {(datetime.today() - self.start_time).total_seconds()}") 161 | 162 | if self.info_enabled: 163 | if STAT.test_cases > self.progress: 164 | self.progress += self.one_percent_progress 165 | self.progress_percent += 1 166 | msg = f"\rProgress: {STAT.test_cases}|{self.progress_percent}%, " 167 | msg += STAT.get_brief() 168 | print(msg + "> Normal execution ", end=self.line_ending, flush=True) 169 | self.msg = msg 170 | 171 | def fuzzer_priming(self, num_violations: int): 172 | if self.info_enabled: 173 | print( 174 | self.msg + "> Priming:" + str(num_violations) + " ", 175 | end=self.line_ending, 176 | flush=True) 177 | 178 | def fuzzer_nesting_increased(self): 179 | if self.info_enabled: 180 | print( 181 | self.msg + "> Trying max nesting:" + str(CONF.model_max_nesting) + " ", 182 | end=self.line_ending, 183 | flush=True) 184 | 185 | def fuzzer_timeout(self): 186 | self.info("fuzzer", "\nTimeout expired") 187 | 188 | def fuzzer_finish(self): 189 | if self.info_enabled: 190 | now = datetime.today() 191 | print("") # new line after the progress bar 192 | if self.stat_enabled: 193 | print(STAT) 194 | print(f"Duration: {(now - self.start_time).total_seconds():.1f}") 195 | print(datetime.today().strftime('Finished at %H:%M:%S')) 196 | 197 | def trc_fuzzer_dump_traces(self, htraces, ctraces): 198 | if __debug__: 199 | if self.fuzzer_trace: 200 | print("") 201 | nprinted = 10 if len(ctraces) > 10 else len(ctraces) 202 | for i in range(nprinted): 203 | print("..............................................................") 204 | print(self.pretty_bitmap(ctraces[i], ctraces[i] > pow(2, 64))) 205 | print(self.pretty_bitmap(htraces[i])) 206 | 207 | def fuzzer_report_violations(self, violation: EquivalenceClass, model): 208 | print("\n\n================================ Violations detected ==========================") 209 | print(" Contract trace (hash):\n") 210 | if violation.ctrace <= pow(2, 64): 211 | print(f" {violation.ctrace:064b}") 212 | else: 213 | print(f" {violation.ctrace % MASK_64BIT:064b} [ns]\n" 214 | f" {(violation.ctrace >> 64) % MASK_64BIT:064b} [s]\n") 215 | print(" Hardware traces:") 216 | for htrace, measurements in violation.htrace_map.items(): 217 | inputs = [m.input_id for m in measurements] 218 | if len(inputs) < 4: 219 | print(f" Inputs {inputs}:") 220 | else: 221 | print(f" Inputs {inputs[:4]} (+ {len(inputs) - 4} ):") 222 | print(f" {self.pretty_bitmap(htrace)}") 223 | print("") 224 | 225 | if __debug__ and self.fuzzer_debug: 226 | # print details 227 | print("================================ Execution Trace ==============================") 228 | for htrace, measurements in violation.htrace_map.items(): 229 | print("---------------------------------------------------------------------------") 230 | print(f"Input #{measurements[0].input_id}") 231 | model_debug_state = self.model_debug 232 | self.model_debug = True 233 | model.trace_test_case([measurements[0].input_], 1) 234 | self.model_debug = model_debug_state 235 | 236 | # ============================================================================================== 237 | # Model 238 | def dbg_model_mem_access(self, normalized_address, val, is_store): 239 | if self.model_debug: 240 | type_ = "store:" if is_store else "load: " 241 | print(f" > {type_} +0x{normalized_address:x} = 0x{val:x}") 242 | 243 | def dbg_model_instruction(self, name, normalized_address, model): 244 | if self.model_debug: 245 | print(f"{normalized_address:2x}: {name}") 246 | model.print_state(oneline=True) 247 | 248 | # ============================================================================================== 249 | # Coverage 250 | def dbg_report_coverage(self, round_id, msg): 251 | if __debug__: 252 | if round_id and round_id % 100 == 0 and self.coverage_debug: 253 | print(f"\nDBG: [coverage] {msg}") 254 | 255 | # ============================================================================================== 256 | # Helpers 257 | def pretty_bitmap(self, bits: int, merged=False): 258 | if not merged: 259 | s = f"{bits:064b}" 260 | else: 261 | s = f"{bits % MASK_64BIT:064b} [ns]\n" \ 262 | f"{(bits >> 64) % MASK_64BIT:064b} [s]" 263 | s = s.replace("0", "_").replace("1", "^") 264 | return s 265 | 266 | 267 | LOGGER = Logger() 268 | 269 | 270 | # ================================================================================================== 271 | # Small helper functions 272 | # ================================================================================================== 273 | def bit_count(n): 274 | count = 0 275 | while n: 276 | count += n & 1 277 | n >>= 1 278 | return count 279 | 280 | 281 | class NotSupportedException(Exception): 282 | pass 283 | -------------------------------------------------------------------------------- /src/coverage.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Various helper functions used by multiple parts of the project 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | from enum import IntEnum 8 | from typing import Dict, Set, List, Optional 9 | 10 | from isa_loader import InstructionSet 11 | from interfaces import Coverage, EquivalenceClass, TestCase, Executor, Model, Analyser, \ 12 | ExecutionTrace, TracedInstruction, Instruction, RegisterOperand, OT 13 | from generator import X86Registers 14 | 15 | from config import CONF, ConfigException 16 | from service import STAT 17 | 18 | 19 | # ================================================================================================== 20 | # Coverage Disabled 21 | # ================================================================================================== 22 | class NoCoverage(Coverage): 23 | """ 24 | A dummy class with empty functions. 25 | Used when fuzzing without coverage 26 | """ 27 | 28 | def load_test_case(self, test_case): 29 | pass 30 | 31 | def generator_hook(self, feedback): 32 | pass 33 | 34 | def model_hook(self, feedback): 35 | pass 36 | 37 | def executor_hook(self, feedback): 38 | pass 39 | 40 | def analyser_hook(self, feedback): 41 | pass 42 | 43 | def get(self) -> int: 44 | return 0 45 | 46 | 47 | # ================================================================================================== 48 | # DependentPairCoverage 49 | # ================================================================================================== 50 | class DT(IntEnum): # Dependency Type 51 | REG_GPR = 1 52 | REG_FLAGS = 2 53 | MEM_LL = 4 54 | MEM_LS = 5 55 | MEM_SL = 6 56 | MEM_SS = 7 57 | CONTROL_DIRECT = 8 58 | CONTROL_COND = 9 59 | 60 | 61 | class DependentPairCoverage(Coverage): 62 | """ Coverage of pairs of instructions with a data or control-flow dependency """ 63 | coverage: Dict[DT, Set[int]] 64 | max_coverage: Dict[DT, int] 65 | execution_traces: List[ExecutionTrace] 66 | test_case: TestCase 67 | 68 | def __init__(self, instruction_set: InstructionSet, executor: Executor, model: Model, 69 | analyser: Analyser): 70 | super().__init__(instruction_set, executor, model, analyser) 71 | self.coverage = {k: set() for k in DT} 72 | self.max_coverage = {} 73 | self._calculate_max_coverage() 74 | 75 | def _update_coverage(self, effective_traces: List[ExecutionTrace]) -> None: 76 | """ The main function of this class - calculates coverage based on the collected traces """ 77 | 78 | # get rid of instrumentation in the traces 79 | # - collect the addresses of instrumentation instructions 80 | instrumentation_addresses = [] 81 | for addr, instr in self.test_case.address_map.items(): 82 | if instr.is_instrumentation: 83 | instrumentation_addresses.append(addr) 84 | # - remove those addresses from traces 85 | filtered_traces = [] 86 | for trace in effective_traces: 87 | filtered_trace = [t for t in trace if t.i_address not in instrumentation_addresses] 88 | filtered_traces.append(filtered_trace) 89 | effective_traces = filtered_traces 90 | 91 | # process all pairs of the executed instructions 92 | addr1: TracedInstruction 93 | addr2: TracedInstruction 94 | for trace in effective_traces: 95 | for addr1, addr2 in zip(trace, trace[1:]): 96 | instr1 = self.test_case.address_map[addr1.i_address] 97 | instr2 = self.test_case.address_map[addr2.i_address] 98 | 99 | type_: Optional[DT] 100 | key = hash(self._get_instruction_key(instr1) + self._get_instruction_key(instr2)) 101 | 102 | # control flow dependency 103 | if instr1.control_flow: 104 | type_ = DT.CONTROL_DIRECT if instr1.category == "BASE-UNCOND_BR" \ 105 | else DT.CONTROL_COND 106 | self.coverage[type_].add(key) 107 | 108 | # potential memory dependency 109 | if addr1.accesses and addr2.accesses: 110 | types = self._search_memory_dependency(addr1, addr2) 111 | for type_ in types: 112 | self.coverage[type_].add(key) 113 | 114 | # potential reg dependency 115 | if self._search_reg_dependency(instr1, instr2): 116 | self.coverage[DT.REG_GPR].add(key) 117 | if self._search_flag_dependency(instr1, instr2): 118 | self.coverage[DT.REG_FLAGS].add(key) 119 | 120 | def _calculate_max_coverage(self): 121 | all_, reg_src, reg_dest, flags_src, flags_dest, mem_src, mem_dest, control_cond = (0, ) * 8 122 | control_direct = 1 123 | 124 | for inst in self.instruction_set.instructions: 125 | all_ += 1 126 | 127 | reg_ops = [r for r in inst.operands + inst.implicit_operands if r.type == OT.REG] 128 | if any(reg.src for reg in reg_ops): 129 | reg_src += 1 130 | if any(reg.dest for reg in reg_ops): 131 | reg_dest += 1 132 | 133 | flag_ops = [r for r in inst.operands + inst.implicit_operands if r.type == OT.FLAGS] 134 | if flag_ops: 135 | has_src, has_dest = False, False 136 | for v in flag_ops[0].values: 137 | if 'r' in v: 138 | has_src = True 139 | if 'w' in v: 140 | has_dest = True 141 | if has_src: 142 | flags_src += 1 143 | if has_dest: 144 | flags_dest += 1 145 | 146 | if inst.has_write: 147 | mem_dest += 1 148 | if [r for r in inst.operands + inst.implicit_operands if r.type == OT.MEM and r.src]: 149 | mem_src += 1 150 | 151 | if inst.control_flow: 152 | if inst.category == "BASE-UNCOND_BR": 153 | control_direct += 1 154 | else: 155 | control_cond += 1 156 | 157 | self.max_coverage[DT.REG_GPR] = reg_src * (reg_dest + mem_src + mem_dest) 158 | self.max_coverage[DT.REG_FLAGS] = flags_src * flags_dest 159 | self.max_coverage[DT.MEM_LL] = mem_src * mem_src 160 | self.max_coverage[DT.MEM_LS] = mem_src * mem_dest 161 | self.max_coverage[DT.MEM_SL] = mem_dest * mem_src 162 | self.max_coverage[DT.MEM_SS] = mem_dest * mem_dest 163 | self.max_coverage[DT.CONTROL_DIRECT] = control_direct * all_ 164 | self.max_coverage[DT.CONTROL_COND] = control_cond * all_ 165 | 166 | def get(self) -> int: 167 | return sum([len(c) for c in self.coverage.values()]) 168 | 169 | def get_brief(self): 170 | flags = (len(self.coverage[DT.REG_FLAGS]) / self.max_coverage[DT.REG_FLAGS]) * 100 171 | grp = (len(self.coverage[DT.REG_GPR]) / self.max_coverage[DT.REG_GPR]) * 100 172 | ll = (len(self.coverage[DT.MEM_LL]) / self.max_coverage[DT.MEM_LL]) * 100 173 | ls = (len(self.coverage[DT.MEM_LS]) / self.max_coverage[DT.MEM_LS]) * 100 174 | sl = (len(self.coverage[DT.MEM_SL]) / self.max_coverage[DT.MEM_SL]) * 100 175 | ss = (len(self.coverage[DT.MEM_SS]) / self.max_coverage[DT.MEM_SS]) * 100 176 | cond = (len(self.coverage[DT.CONTROL_COND]) / self.max_coverage[DT.CONTROL_COND]) * 100 177 | dire = (len(self.coverage[DT.CONTROL_DIRECT]) / self.max_coverage[DT.CONTROL_DIRECT]) * 100 178 | return f"{flags:.2f}, {grp:.2f}, {ll:.2f}, {ls:.2f}," \ 179 | f" {sl:.2f}, {ss:.2f}, {cond:.2f}, {dire:.2f}" 180 | 181 | def load_test_case(self, test_case: TestCase): 182 | self.test_case = test_case 183 | 184 | def model_hook(self, execution_traces: List[ExecutionTrace]): 185 | self.execution_traces = execution_traces 186 | 187 | def analyser_hook(self, classes: List[EquivalenceClass]): 188 | # ignore those traces that belong to ineffective classes 189 | effective_traces = [] 190 | for eq_cls in classes: 191 | if len(eq_cls) > 1: 192 | member_input_id = eq_cls.measurements[0].input_id 193 | effective_traces.append(self.execution_traces[member_input_id]) 194 | if not effective_traces: 195 | return 196 | 197 | # we're done with this test case and are ready to collect coverage 198 | self._update_coverage(effective_traces) 199 | STAT.coverage = self.get() 200 | return 201 | 202 | def executor_hook(self, _): 203 | pass 204 | 205 | def _get_instruction_key(self, instruction: Instruction) -> str: 206 | key = instruction.name 207 | for op in instruction.operands + instruction.implicit_operands: 208 | key += "-" + str(op.width) + str(op.type) 209 | return key 210 | 211 | def _search_memory_dependency(self, traced_instr1: TracedInstruction, 212 | traced_instr2: TracedInstruction) -> List[DT]: 213 | read_addresses1 = [] 214 | write_addresses1 = [] 215 | for addr in traced_instr1.accesses: 216 | if addr.is_store: 217 | write_addresses1.append(addr.m_address) 218 | else: 219 | read_addresses1.append(addr.m_address) 220 | 221 | read_addresses2 = [] 222 | write_addresses2 = [] 223 | for addr in traced_instr2.accesses: 224 | if addr.is_store: 225 | read_addresses2.append(addr.m_address) 226 | else: 227 | write_addresses2.append(addr.m_address) 228 | 229 | types = [] 230 | if any(i in read_addresses2 for i in read_addresses1): 231 | types.append(DT.MEM_LL) 232 | 233 | if any(i in write_addresses2 for i in read_addresses1): 234 | types.append(DT.MEM_LS) 235 | 236 | if any(i in read_addresses2 for i in write_addresses1): 237 | types.append(DT.MEM_SL) 238 | 239 | if any(i in write_addresses2 for i in write_addresses1): 240 | types.append(DT.MEM_SS) 241 | 242 | return types 243 | 244 | def _search_reg_dependency(self, inst1: Instruction, inst2: Instruction) -> Optional[DT]: 245 | # normal register dependencies 246 | dest_regs = [ 247 | r.value for r in inst1.get_dest_operands(True) if isinstance(r, RegisterOperand) 248 | ] 249 | src_regs = [r.value for r in inst2.get_src_operands(True) if isinstance(r, RegisterOperand)] 250 | dest_regs = [X86Registers.gpr_normalized[r] for r in dest_regs] 251 | src_regs = [X86Registers.gpr_normalized[r] for r in src_regs] 252 | for r in dest_regs: 253 | if r in src_regs: 254 | return DT.REG_GPR 255 | 256 | # address dependency 257 | mem_operands = [m.value for m in inst2.get_mem_operands()] 258 | for r in dest_regs: 259 | for mem in mem_operands: 260 | if r in mem: 261 | return DT.REG_GPR 262 | 263 | return None 264 | 265 | def _search_flag_dependency(self, instr1: Instruction, instr2: Instruction) -> Optional[DT]: 266 | flags1 = instr1.get_flags_operand() 267 | flags2 = instr2.get_flags_operand() 268 | if flags1 and flags2 and flags2.is_dependent(flags1): 269 | return DT.REG_FLAGS 270 | 271 | return None 272 | 273 | def _dbg_print_coverage_by_type(self): 274 | print("") 275 | for k in self.coverage: 276 | size = len(self.coverage[k]) 277 | ratio = (size / self.max_coverage[k]) * 100 278 | print(f"- {str(k)}: {size} [{ratio:.3}%]") 279 | 280 | 281 | def get_coverage(instruction_set: InstructionSet, executor: Executor, model: Model, 282 | analyser: Analyser) -> Coverage: 283 | if CONF.coverage_type == 'dependent-pairs': 284 | return DependentPairCoverage(instruction_set, executor, model, analyser) 285 | elif CONF.coverage_type == 'none': 286 | return NoCoverage(instruction_set, executor, model, analyser) 287 | else: 288 | ConfigException("unknown value of `coverage_type` configuration option") 289 | exit(1) 290 | -------------------------------------------------------------------------------- /src/fuzzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: Fuzzing Orchestration 3 | 4 | Copyright (C) Microsoft Corporation 5 | SPDX-License-Identifier: MIT 6 | """ 7 | import shutil 8 | from pathlib import Path 9 | from datetime import datetime 10 | from typing import Optional, List, Dict, Tuple 11 | from copy import copy 12 | 13 | from interfaces import CTrace, HTrace, Input, InputTaint, EquivalenceClass, TestCase, Generator, \ 14 | InputGenerator, Model, Executor, Analyser, Coverage, InputID 15 | from generator import get_generator 16 | from input_generator import get_input_generator 17 | from model import get_model 18 | from executor import get_executor 19 | from analyser import get_analyser 20 | from coverage import get_coverage 21 | from isa_loader import InstructionSet 22 | 23 | from config import CONF 24 | from service import STAT, LOGGER, TWOS_COMPLEMENT_MASK_64, bit_count 25 | 26 | Multiprimer = Dict[Input, List[Input]] 27 | 28 | 29 | class Fuzzer: 30 | instruction_set: InstructionSet 31 | existing_test_case: str 32 | 33 | generator: Generator 34 | input_gen: InputGenerator 35 | executor: Executor 36 | model: Model 37 | analyser: Analyser 38 | coverage: Coverage 39 | 40 | def __init__(self, instruction_set_spec: str, work_dir: str, existing_test_case: str = ""): 41 | self.existing_test_case = existing_test_case 42 | if existing_test_case: 43 | CONF._no_generation = True 44 | CONF.gpr_blocklist = [] 45 | CONF.instruction_blocklist = [] 46 | 47 | self.instruction_set = InstructionSet(instruction_set_spec, CONF.supported_categories) 48 | self.work_dir = work_dir 49 | 50 | def start(self, num_test_cases: int, num_inputs: int, timeout: int, nonstop: bool = False): 51 | start_time = datetime.today() 52 | LOGGER.fuzzer_start(num_test_cases, start_time) 53 | 54 | # create all main modules 55 | self.initialize_modules() 56 | 57 | for i in range(num_test_cases): 58 | LOGGER.fuzzer_start_round(i) 59 | LOGGER.dbg_report_coverage(i, self.coverage.get_brief()) 60 | 61 | # Generate a test case 62 | if not self.existing_test_case: 63 | test_case = self.generator.create_test_case('generated.asm') 64 | else: 65 | test_case = self.generator.parse_existing_test_case(self.existing_test_case) 66 | 67 | # Prepare inputs 68 | inputs: List[Input] = self.input_gen.generate(CONF.input_gen_seed, num_inputs) 69 | 70 | # Fuzz the test case 71 | violation = self.fuzzing_round(test_case, inputs) 72 | STAT.test_cases += 1 73 | 74 | if violation: 75 | LOGGER.fuzzer_report_violations(violation, self.model) 76 | self.store_test_case(test_case, False) 77 | STAT.violations += 1 78 | if not nonstop: 79 | break 80 | 81 | # stop fuzzing after a timeout 82 | if timeout: 83 | now = datetime.today() 84 | if (now - start_time).total_seconds() > timeout: 85 | LOGGER.fuzzer_timeout() 86 | break 87 | 88 | LOGGER.fuzzer_finish() 89 | 90 | def fuzzing_round(self, test_case: TestCase, inputs: List[Input]) -> Optional[EquivalenceClass]: 91 | self.model.load_test_case(test_case) 92 | self.executor.load_test_case(test_case) 93 | self.coverage.load_test_case(test_case) 94 | 95 | # by default, we test without nested misprediction, 96 | # but retry with nesting upon a violation 97 | violations: List[EquivalenceClass] = [] 98 | boosted_inputs: List[Input] = [] 99 | for nesting in [1, CONF.model_max_nesting]: 100 | boosted_inputs = self.boost_inputs(inputs, nesting) 101 | STAT.num_inputs += len(boosted_inputs) 102 | 103 | # get traces 104 | ctraces: List[CTrace] = self.model.trace_test_case(boosted_inputs, nesting) 105 | htraces: List[HTrace] = self.executor.trace_test_case(boosted_inputs) 106 | LOGGER.trc_fuzzer_dump_traces(htraces, ctraces) 107 | 108 | # Check for violations 109 | violations = self.analyser.filter_violations(boosted_inputs, ctraces, htraces, True) 110 | 111 | # nothing detected? -> we are done here, move to next test case 112 | if not violations: 113 | return None 114 | 115 | # sequential contract? -> no point in trying higher nesting 116 | if 'seq' in CONF.contract_execution_clause: 117 | break 118 | 119 | # otherwise, try higher nesting 120 | if nesting == 1: 121 | LOGGER.fuzzer_nesting_increased() 122 | 123 | if CONF.no_priming: 124 | return violations[-1] 125 | 126 | # Try priming the inputs that disagree with the other ones within the same eq. class 127 | STAT.required_priming += 1 128 | while violations: 129 | LOGGER.fuzzer_priming(len(violations)) 130 | violation: EquivalenceClass = violations.pop() 131 | if self.survives_priming(violation, boosted_inputs): 132 | break 133 | else: 134 | # all violations were cleaned. all good 135 | return None 136 | 137 | # Violation survived priming. Report it 138 | return violation 139 | 140 | def initialize_modules(self): 141 | """ create all main modules """ 142 | self.generator = get_generator(self.instruction_set) 143 | self.input_gen: InputGenerator = get_input_generator() 144 | self.executor: Executor = get_executor() 145 | self.model: Model = get_model(self.executor.read_base_addresses()) 146 | self.analyser: Analyser = get_analyser() 147 | self.coverage: Coverage = get_coverage(self.instruction_set, self.executor, self.model, 148 | self.analyser) 149 | 150 | def boost_inputs(self, inputs: List[Input], nesting: int) -> List[Input]: 151 | taints: List[InputTaint] 152 | taints = self.model.get_taints(inputs, nesting) 153 | 154 | # ensure that we have many inputs in each input classes 155 | boosted_inputs: List[Input] = list(inputs) # make a copy 156 | for _ in range(CONF.inputs_per_class - 1): 157 | boosted_inputs += self.input_gen.extend_equivalence_classes(inputs, taints) 158 | return boosted_inputs 159 | 160 | def store_test_case(self, test_case: TestCase, require_retires: bool): 161 | if not self.work_dir: 162 | return 163 | 164 | type_ = "retry" if require_retires else "violation" 165 | timestamp = datetime.today().strftime('%H%M%S-%d-%m-%y') 166 | name = type_ + timestamp + ".asm" 167 | Path(self.work_dir).mkdir(exist_ok=True) 168 | shutil.copy2(test_case.asm_path, self.work_dir + "/" + name) 169 | 170 | if not Path(self.work_dir + "/config.yaml").exists: 171 | shutil.copy2(CONF.config_path, self.work_dir + "/config.yaml") 172 | 173 | # ============================================================================================== 174 | # Priming algorithm 175 | def survives_priming(self, org_violation: EquivalenceClass, all_inputs: List[Input]) -> bool: 176 | """ 177 | Try priming the inputs that caused the violations 178 | return: True if the violation survived priming 179 | """ 180 | violation = copy(org_violation) 181 | ordered_htraces = sorted( 182 | violation.htrace_map.keys(), key=lambda x: bit_count(x), reverse=False) 183 | 184 | for current_htrace in ordered_htraces: 185 | current_input_id = violation.htrace_map[current_htrace][-1].input_id 186 | 187 | # list of inputs that produced a different HTrace 188 | input_ids_to_test: List[InputID] = [ 189 | m.input_id for m in violation.measurements if m.htrace != current_htrace 190 | ] 191 | 192 | # insert the tested inputs into their places 193 | for input_id in input_ids_to_test: 194 | primer = list(all_inputs) 195 | primer[current_input_id] = all_inputs[input_id] 196 | 197 | # try priming 198 | if not self.primer_is_effective(primer, [current_input_id], current_htrace): 199 | return True 200 | 201 | return False 202 | 203 | def primer_is_effective(self, inputs: List[Input], positions: List[InputID], 204 | expected_htrace: HTrace) -> bool: 205 | htraces: List[HTrace] = self.executor.trace_test_case(inputs, CONF.executor_repetitions) 206 | for i, htrace in enumerate(htraces): 207 | if i not in positions: 208 | continue 209 | if htrace == expected_htrace: 210 | continue 211 | 212 | # if the primed measurement triggered more speculation, it's ok 213 | if (htrace ^ TWOS_COMPLEMENT_MASK_64) & expected_htrace == 0: 214 | continue 215 | 216 | return False 217 | return True 218 | 219 | # ============================================================================================== 220 | # Batched priming algoritm - deprecated??? 221 | def survives_priming_batched(self, org_violation: EquivalenceClass, 222 | all_inputs: List[Input]) -> bool: 223 | violation = copy(org_violation) 224 | ordered_htraces = sorted( 225 | violation.htrace_map.keys(), key=lambda x: bit_count(x), reverse=False) 226 | 227 | for current_htrace in ordered_htraces: 228 | current_input_id = violation.htrace_map[current_htrace][-1].input_id 229 | 230 | # list of inputs that produced a different HTrace 231 | input_ids_to_test: List[InputID] = [ 232 | m.input_id for m in violation.measurements if m.htrace != current_htrace 233 | ] 234 | 235 | # create a primer that would cover all the conflicting inputs at the same time 236 | batch_primer, primed_ids = self.build_batch_primer(all_inputs, 237 | current_input_id, current_htrace, 238 | len(input_ids_to_test)) 239 | if not batch_primer: 240 | STAT.priming_errors += 1 241 | # self.store_test_case(self.test_case, True) 242 | return False 243 | 244 | # insert the tested inputs into their places 245 | assert len(primed_ids) == len(input_ids_to_test) 246 | for input_id, primer_id in zip(input_ids_to_test, primed_ids): 247 | batch_primer[primer_id] = all_inputs[input_id] 248 | 249 | # try priming 250 | if not self.primer_is_effective(batch_primer, primed_ids, current_htrace): 251 | return True 252 | 253 | return False 254 | 255 | def build_batch_primer(self, inputs: List[Input], target_input_id: InputID, 256 | expected_htrace: HTrace, 257 | num_primed_inputs: int) -> Tuple[List[Input], List[InputID]]: 258 | # the first size to be tested 259 | primer_size = CONF.min_primer_size % len(inputs) + 1 260 | 261 | while True: 262 | # print(f"Trying primer {primer_size}") 263 | # build a set of priming inputs (i.e., multiprimer) 264 | if primer_size <= target_input_id: 265 | primer = inputs[target_input_id - primer_size:target_input_id + 1] 266 | else: 267 | primer = inputs[target_input_id - primer_size:] + inputs[:target_input_id + 1] 268 | assert len(primer) == primer_size + 1 269 | assert primer[-1].seed == inputs[target_input_id].seed 270 | 271 | batch_primer = primer * num_primed_inputs 272 | # print(target_input_id, primer_size, len(inputs), len(primer)) 273 | primed_ids = list( 274 | range(primer_size, num_primed_inputs * (primer_size + 1), primer_size + 1)) 275 | # print(primed_ids) 276 | 277 | # check if the hardware trace of the target_id matches 278 | # the hardware trace received with the primer 279 | if self.primer_is_effective(batch_primer, primed_ids, expected_htrace): 280 | return batch_primer, primed_ids 281 | 282 | # if we failed, try a larger primer 283 | new_size = primer_size * 2 284 | 285 | # if we just wrapped around, try all original preceding inputs as primer 286 | if new_size > target_input_id and primer_size < target_input_id: 287 | primer_size = target_input_id 288 | else: 289 | primer_size = new_size 290 | 291 | # if a larger primer is allowed, try adding more inputs 292 | if primer_size > CONF.max_primer_size: 293 | # otherwise, we failed to find a primer 294 | LOGGER.waring("fuzzer", "Failed to build a primer - max_primer_size reached") 295 | return [], [] 296 | 297 | # run out of inputs to test? 298 | if primer_size >= len(inputs): 299 | LOGGER.waring("fuzzer", "Insufficient inputs to build a primer") 300 | return [], [] 301 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = false 9 | max_line_length = 100 10 | tab_width = 4 11 | 12 | [*.json] 13 | indent_size = 2 14 | keep_blank_lines_in_code = 0 15 | keep_indents_on_empty_lines = false 16 | keep_line_breaks = true 17 | space_after_colon = true 18 | space_after_comma = true 19 | space_before_colon = true 20 | space_before_comma = false 21 | spaces_within_braces = false 22 | spaces_within_brackets = false 23 | wrap_long_lines = false 24 | 25 | [{*.bash,*.zsh,*.sh,*.bats}] 26 | tab_width = 2 27 | binary_ops_start_line = false 28 | keep_column_alignment_padding = false 29 | minify_program = false 30 | redirect_followed_by_space = false 31 | switch_cases_indented = false 32 | 33 | [{*.cc,*.mm,*.tcc,*.hpp,*.cpp,*.ii,*.hxx,*.hp,*.hh,*.cxx,*.m,*.i,*.h,*.c,*.h++,*.ipp,*.icc,*.c++,*.pch,*.ino,*.inl,*.cp,*.inc}] 34 | add_brief_tag = false 35 | add_getter_prefix = true 36 | add_setter_prefix = true 37 | align_dictionary_pair_values = false 38 | align_group_field_declarations = false 39 | align_init_list_in_columns = false 40 | align_multiline_array_initializer_expression = true 41 | align_multiline_assignment = true 42 | align_multiline_binary_operation = false 43 | align_multiline_chained_methods = false 44 | align_multiline_for = true 45 | align_multiline_ternary_operation = true 46 | array_initializer_comma_on_next_line = false 47 | array_initializer_new_line_after_left_brace = false 48 | array_initializer_right_brace_on_new_line = false 49 | array_initializer_wrap = normal 50 | assignment_wrap = normal 51 | binary_operation_sign_on_next_line = true 52 | binary_operation_wrap = normal 53 | blank_lines_after_class_header = 0 54 | blank_lines_after_imports = 0 55 | blank_lines_around_class = 0 56 | blank_lines_around_field = 0 57 | blank_lines_around_field_in_interface = 0 58 | blank_lines_around_method = 0 59 | blank_lines_around_method_in_interface = 0 60 | blank_lines_around_namespace = 0 61 | blank_lines_around_properties_in_declaration = 0 62 | blank_lines_around_properties_in_interface = 0 63 | blank_lines_before_imports = 0 64 | blank_lines_before_method_body = 0 65 | block_brace_placement = end_of_line 66 | block_brace_style = end_of_line 67 | block_comment_at_first_column = true 68 | catch_on_new_line = false 69 | class_brace_style = end_of_line 70 | class_constructor_init_list_align_multiline = true 71 | class_constructor_init_list_comma_on_next_line = false 72 | class_constructor_init_list_new_line_after_colon = never 73 | class_constructor_init_list_new_line_before_colon = if_long 74 | class_constructor_init_list_wrap = on_every_item 75 | copy_is_deep = false 76 | create_interface_for_categories = true 77 | declare_generated_methods = true 78 | description_include_member_names = true 79 | discharged_short_ternary_operator = false 80 | do_not_add_breaks = false 81 | do_while_brace_force = never 82 | else_on_new_line = false 83 | enum_constants_comma_on_next_line = false 84 | enum_constants_wrap = on_every_item 85 | for_brace_force = never 86 | for_statement_new_line_after_left_paren = false 87 | for_statement_right_paren_on_new_line = false 88 | for_statement_wrap = normal 89 | function_brace_placement = end_of_line 90 | function_call_arguments_align_multiline = true 91 | function_call_arguments_align_multiline_pars = false 92 | function_call_arguments_comma_on_next_line = false 93 | function_call_arguments_new_line_after_lpar = false 94 | function_call_arguments_new_line_before_rpar = false 95 | function_call_arguments_wrap = on_every_item 96 | function_non_top_after_return_type_wrap = off 97 | function_parameters_align_multiline = true 98 | function_parameters_align_multiline_pars = false 99 | function_parameters_comma_on_next_line = false 100 | function_parameters_new_line_after_lpar = false 101 | function_parameters_new_line_before_rpar = false 102 | function_parameters_wrap = on_every_item 103 | function_top_after_return_type_wrap = off 104 | generate_additional_eq_operators = true 105 | generate_additional_rel_operators = true 106 | generate_class_constructor = true 107 | generate_comparison_operators_use_std_tie = false 108 | generate_instance_variables_for_properties = ask 109 | generate_operators_as_members = true 110 | header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} 111 | if_brace_force = never 112 | in_line_short_ternary_operator = true 113 | indent_block_comment = true 114 | indent_c_struct_members = 4 115 | indent_case_from_switch = true 116 | indent_class_members = 4 117 | indent_directive_as_code = false 118 | indent_implementation_members = 0 119 | indent_inside_code_block = 4 120 | indent_interface_members = 0 121 | indent_interface_members_except_ivars_block = false 122 | indent_namespace_members = 0 123 | indent_preprocessor_directive = 0 124 | indent_visibility_keywords = 2 125 | insert_override = true 126 | insert_virtual_with_override = false 127 | introduce_auto_vars = false 128 | introduce_const_params = false 129 | introduce_const_vars = false 130 | introduce_generate_property = false 131 | introduce_generate_synthesize = true 132 | introduce_globals_to_header = true 133 | introduce_prop_to_private_category = false 134 | introduce_static_consts = true 135 | introduce_use_ns_types = false 136 | ivars_prefix = _ 137 | keep_blank_lines_before_end = 2 138 | keep_blank_lines_before_right_brace = 1 139 | keep_blank_lines_in_code = 1 140 | keep_blank_lines_in_declarations = 1 141 | keep_case_expressions_in_one_line = false 142 | keep_control_statement_in_one_line = true 143 | keep_directive_at_first_column = true 144 | keep_first_column_comment = true 145 | keep_line_breaks = true 146 | keep_nested_namespaces_in_one_line = false 147 | keep_simple_blocks_in_one_line = true 148 | keep_simple_methods_in_one_line = true 149 | keep_structures_in_one_line = true 150 | lambda_capture_list_align_multiline = false 151 | lambda_capture_list_align_multiline_bracket = false 152 | lambda_capture_list_comma_on_next_line = false 153 | lambda_capture_list_new_line_after_lbracket = false 154 | lambda_capture_list_new_line_before_rbracket = false 155 | lambda_capture_list_wrap = off 156 | line_comment_add_space = false 157 | line_comment_at_first_column = false 158 | method_brace_placement = end_of_line 159 | method_call_arguments_align_by_colons = true 160 | method_call_arguments_align_multiline = false 161 | method_call_arguments_special_dictionary_pairs_treatment = true 162 | method_call_arguments_wrap = off 163 | method_call_chain_wrap = off 164 | method_parameters_align_by_colons = true 165 | method_parameters_align_multiline = false 166 | method_parameters_wrap = off 167 | namespace_brace_placement = end_of_line 168 | parentheses_expression_new_line_after_left_paren = false 169 | parentheses_expression_right_paren_on_new_line = false 170 | place_assignment_sign_on_next_line = false 171 | property_nonatomic = true 172 | put_ivars_to_implementation = true 173 | refactor_compatibility_aliases_and_classes = true 174 | refactor_properties_and_ivars = true 175 | release_style = ivar 176 | retain_object_parameters_in_constructor = true 177 | semicolon_after_method_signature = false 178 | shift_operation_align_multiline = true 179 | shift_operation_wrap = normal 180 | show_non_virtual_functions = false 181 | space_after_colon = true 182 | space_after_colon_in_selector = false 183 | space_after_comma = true 184 | space_after_cup_in_blocks = false 185 | space_after_dictionary_literal_colon = true 186 | space_after_for_semicolon = true 187 | space_after_init_list_colon = true 188 | space_after_method_parameter_type_parentheses = false 189 | space_after_method_return_type_parentheses = false 190 | space_after_pointer_in_declaration = false 191 | space_after_quest = true 192 | space_after_reference_in_declaration = false 193 | space_after_reference_in_rvalue = false 194 | space_after_structures_rbrace = true 195 | space_after_superclass_colon = true 196 | space_after_type_cast = true 197 | space_after_visibility_sign_in_method_declaration = true 198 | space_before_autorelease_pool_lbrace = true 199 | space_before_catch_keyword = true 200 | space_before_catch_left_brace = true 201 | space_before_catch_parentheses = true 202 | space_before_category_parentheses = true 203 | space_before_chained_send_message = true 204 | space_before_class_left_brace = true 205 | space_before_colon = true 206 | space_before_comma = false 207 | space_before_dictionary_literal_colon = false 208 | space_before_do_left_brace = true 209 | space_before_else_keyword = true 210 | space_before_else_left_brace = true 211 | space_before_for_left_brace = true 212 | space_before_for_parentheses = true 213 | space_before_for_semicolon = false 214 | space_before_if_left_brace = true 215 | space_before_if_parentheses = true 216 | space_before_init_list = false 217 | space_before_init_list_colon = true 218 | space_before_method_call_parentheses = false 219 | space_before_method_left_brace = true 220 | space_before_method_parentheses = false 221 | space_before_namespace_lbrace = true 222 | space_before_pointer_in_declaration = true 223 | space_before_property_attributes_parentheses = false 224 | space_before_protocols_brackets = true 225 | space_before_quest = true 226 | space_before_reference_in_declaration = true 227 | space_before_superclass_colon = true 228 | space_before_switch_left_brace = true 229 | space_before_switch_parentheses = true 230 | space_before_template_call_lt = false 231 | space_before_template_declaration_lt = false 232 | space_before_try_left_brace = true 233 | space_before_while_keyword = true 234 | space_before_while_left_brace = true 235 | space_before_while_parentheses = true 236 | space_between_adjacent_brackets = false 237 | space_between_operator_and_punctuator = false 238 | space_within_empty_array_initializer_braces = false 239 | spaces_around_additive_operators = true 240 | spaces_around_assignment_operators = true 241 | spaces_around_bitwise_operators = true 242 | spaces_around_equality_operators = true 243 | spaces_around_lambda_arrow = true 244 | spaces_around_logical_operators = true 245 | spaces_around_multiplicative_operators = true 246 | spaces_around_pm_operators = false 247 | spaces_around_relational_operators = true 248 | spaces_around_shift_operators = true 249 | spaces_around_unary_operator = false 250 | spaces_within_array_initializer_braces = false 251 | spaces_within_braces = true 252 | spaces_within_brackets = false 253 | spaces_within_cast_parentheses = false 254 | spaces_within_catch_parentheses = false 255 | spaces_within_category_parentheses = false 256 | spaces_within_empty_braces = false 257 | spaces_within_empty_function_call_parentheses = false 258 | spaces_within_empty_function_declaration_parentheses = false 259 | spaces_within_empty_lambda_capture_list_bracket = false 260 | spaces_within_empty_template_call_ltgt = false 261 | spaces_within_empty_template_declaration_ltgt = false 262 | spaces_within_for_parentheses = false 263 | spaces_within_function_call_parentheses = false 264 | spaces_within_function_declaration_parentheses = false 265 | spaces_within_if_parentheses = false 266 | spaces_within_lambda_capture_list_bracket = false 267 | spaces_within_method_parameter_type_parentheses = false 268 | spaces_within_method_return_type_parentheses = false 269 | spaces_within_parentheses = false 270 | spaces_within_property_attributes_parentheses = false 271 | spaces_within_protocols_brackets = false 272 | spaces_within_send_message_brackets = false 273 | spaces_within_switch_parentheses = false 274 | spaces_within_template_call_ltgt = false 275 | spaces_within_template_declaration_ltgt = false 276 | spaces_within_template_double_gt = true 277 | spaces_within_while_parentheses = false 278 | special_else_if_treatment = true 279 | superclass_list_after_colon = never 280 | superclass_list_align_multiline = true 281 | superclass_list_before_colon = if_long 282 | superclass_list_comma_on_next_line = false 283 | superclass_list_wrap = on_every_item 284 | tag_prefix_of_block_comment = at 285 | tag_prefix_of_line_comment = back_slash 286 | template_call_arguments_align_multiline = true 287 | template_call_arguments_align_multiline_pars = false 288 | template_call_arguments_comma_on_next_line = false 289 | template_call_arguments_new_line_after_lt = false 290 | template_call_arguments_new_line_before_gt = false 291 | template_call_arguments_wrap = on_every_item 292 | template_declaration_function_body_indent = false 293 | template_declaration_function_wrap = split_into_lines 294 | template_declaration_struct_body_indent = false 295 | template_declaration_struct_wrap = split_into_lines 296 | template_parameters_align_multiline = false 297 | template_parameters_align_multiline_pars = false 298 | template_parameters_comma_on_next_line = false 299 | template_parameters_new_line_after_lt = false 300 | template_parameters_new_line_before_gt = false 301 | template_parameters_wrap = off 302 | ternary_operation_signs_on_next_line = true 303 | ternary_operation_wrap = normal 304 | type_qualifiers_placement = before 305 | use_modern_casts = true 306 | use_setters_in_constructor = true 307 | while_brace_force = never 308 | while_on_new_line = false 309 | wrap_property_declaration = off 310 | 311 | [{*.htm,*.sht,*.html,*.shtm,*.shtml}] 312 | add_new_line_before_tags = body,div,p,form,h1,h2,h3 313 | align_attributes = true 314 | align_text = false 315 | attribute_wrap = normal 316 | block_comment_at_first_column = true 317 | do_not_align_children_of_min_lines = 0 318 | do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p 319 | do_not_indent_children_of_tags = html,body,thead,tbody,tfoot 320 | enforce_quotes = false 321 | inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var 322 | keep_blank_lines = 2 323 | keep_indents_on_empty_lines = false 324 | keep_line_breaks = true 325 | keep_line_breaks_in_text = true 326 | keep_whitespaces = false 327 | keep_whitespaces_inside = span,pre,textarea 328 | line_comment_at_first_column = true 329 | new_line_after_last_attribute = never 330 | new_line_before_first_attribute = never 331 | quote_style = double 332 | remove_new_line_before_tags = br 333 | space_after_tag_name = false 334 | space_around_equality_in_attribute = false 335 | space_inside_empty_tag = false 336 | text_wrap = normal 337 | 338 | [{*.pyw,*.py}] 339 | align_collections_and_comprehensions = true 340 | align_multiline_imports = true 341 | align_multiline_parameters = true 342 | align_multiline_parameters_in_calls = true 343 | blank_line_at_file_end = true 344 | blank_lines_after_imports = 1 345 | blank_lines_after_local_imports = 0 346 | blank_lines_around_class = 1 347 | blank_lines_around_method = 1 348 | blank_lines_around_top_level_classes_functions = 2 349 | blank_lines_before_first_method = 0 350 | dict_alignment = 0 351 | dict_new_line_after_left_brace = false 352 | dict_new_line_before_right_brace = false 353 | dict_wrapping = 1 354 | from_import_new_line_after_left_parenthesis = false 355 | from_import_new_line_before_right_parenthesis = false 356 | from_import_parentheses_force_if_multiline = false 357 | from_import_trailing_comma_if_multiline = false 358 | from_import_wrapping = 1 359 | hang_closing_brackets = false 360 | keep_blank_lines_in_code = 1 361 | keep_blank_lines_in_declarations = 1 362 | keep_indents_on_empty_lines = false 363 | keep_line_breaks = true 364 | new_line_after_colon = false 365 | new_line_after_colon_multi_clause = true 366 | optimize_imports_always_split_from_imports = false 367 | optimize_imports_case_insensitive_order = false 368 | optimize_imports_join_from_imports_with_same_source = false 369 | optimize_imports_sort_by_type_first = true 370 | optimize_imports_sort_imports = true 371 | optimize_imports_sort_names_in_from_imports = false 372 | space_after_comma = true 373 | space_after_number_sign = true 374 | space_after_py_colon = true 375 | space_before_backslash = true 376 | space_before_comma = false 377 | space_before_for_semicolon = false 378 | space_before_lbracket = false 379 | space_before_method_call_parentheses = false 380 | space_before_method_parentheses = false 381 | space_before_number_sign = true 382 | space_before_py_colon = false 383 | space_within_empty_method_call_parentheses = false 384 | space_within_empty_method_parentheses = false 385 | spaces_around_additive_operators = true 386 | spaces_around_assignment_operators = true 387 | spaces_around_bitwise_operators = true 388 | spaces_around_eq_in_keyword_argument = false 389 | spaces_around_eq_in_named_parameter = false 390 | spaces_around_equality_operators = true 391 | spaces_around_multiplicative_operators = true 392 | spaces_around_power_operator = true 393 | spaces_around_relational_operators = true 394 | spaces_around_shift_operators = true 395 | spaces_within_braces = false 396 | spaces_within_brackets = false 397 | spaces_within_method_call_parentheses = false 398 | spaces_within_method_parentheses = false 399 | use_continuation_indent_for_arguments = false 400 | use_continuation_indent_for_collection_and_comprehensions = false 401 | wrap_long_lines = false 402 | 403 | [{.clang-format,.clang-tidy,_clang-format,*.yml,*.yaml}] 404 | indent_size = 2 405 | keep_indents_on_empty_lines = false 406 | keep_line_breaks = true 407 | 408 | [{CMakeLists.txt,*.cmake}] 409 | align_multiline_parameters_in_calls = false 410 | force_commands_case = 2 411 | keep_blank_lines_in_code = 2 412 | space_before_for_parentheses = true 413 | space_before_if_parentheses = true 414 | space_before_method_call_parentheses = false 415 | space_before_method_parentheses = false 416 | space_before_while_parentheses = true 417 | spaces_within_for_parentheses = false 418 | spaces_within_if_parentheses = false 419 | spaces_within_method_call_parentheses = false 420 | spaces_within_method_parentheses = false 421 | spaces_within_while_parentheses = false 422 | --------------------------------------------------------------------------------