├── .dockerignore ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── ant ├── .gitignore ├── Makefile ├── README.md └── src │ ├── common.h │ ├── ld.c │ └── tls.c ├── examples ├── helloworld │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── main.c │ ├── run-helloworld.rs │ └── syscall.s ├── musl │ ├── README.md │ ├── build.sh │ ├── byte-level-permissions │ │ ├── memchr.patch │ │ ├── memcpy.patch │ │ ├── memmove.patch │ │ ├── memset.patch │ │ ├── stpcpy.patch │ │ ├── strchr.patch │ │ └── strlen.patch │ ├── cancel.patch │ ├── cmsg.patch │ ├── glibc-exports.patch │ ├── libpthread.patch │ ├── libresolv.patch │ └── tls.patch ├── readelf │ ├── .gitignore │ ├── README.md │ ├── binutils.patch │ ├── build.sh │ └── readelf-fuzzer.rs └── readme-demo │ ├── .gitignore │ ├── README.md │ ├── demo.rs │ └── test.c ├── logo.png ├── rustfmt.toml ├── squid ├── Cargo.toml └── src │ ├── backends │ ├── backend.rs │ ├── clang │ │ ├── address.rs │ │ ├── backend.rs │ │ ├── codegen │ │ │ ├── lifter.rs │ │ │ ├── mod.rs │ │ │ └── subcfg.rs │ │ ├── concretize.rs │ │ ├── event.rs │ │ ├── exec.rs │ │ ├── heap.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── perms.rs │ │ ├── preprocess.rs │ │ ├── registers.rs │ │ ├── runtime.rs │ │ ├── symbol.rs │ │ └── variables.rs │ └── mod.rs │ ├── compiler.rs │ ├── event.rs │ ├── frontend │ ├── ao │ │ ├── cfg.rs │ │ ├── engine.rs │ │ ├── error.rs │ │ ├── events.rs │ │ ├── func.rs │ │ ├── lifter.rs │ │ ├── mod.rs │ │ └── ops.rs │ ├── chunk.rs │ ├── dependency.rs │ ├── elf.rs │ ├── error.rs │ ├── fixedvec.rs │ ├── idmap.rs │ ├── image.rs │ ├── mod.rs │ ├── perms.rs │ ├── pointer.rs │ ├── reloc.rs │ ├── section.rs │ ├── symbol.rs │ └── symbolization_passes │ │ ├── arith.rs │ │ ├── deadcode.rs │ │ ├── empty.rs │ │ ├── metadata.rs │ │ ├── mod.rs │ │ ├── propagate.rs │ │ ├── regcache.rs │ │ ├── relax.rs │ │ └── symbolize.rs │ ├── kernel │ ├── fs.rs │ ├── linux.rs │ ├── mod.rs │ └── structs.rs │ ├── lib.rs │ ├── listing.rs │ ├── logger.rs │ ├── passes │ ├── asan.rs │ ├── breakpoint.rs │ ├── dot.rs │ ├── mod.rs │ ├── pass.rs │ └── verify.rs │ ├── riscv │ ├── fflags.rs │ ├── ieee754.rs │ ├── instr.rs │ ├── mod.rs │ ├── register.rs │ ├── rm.rs │ └── syscalls.rs │ └── runtime.rs ├── squid_ewe ├── Cargo.toml ├── README.md └── src │ ├── asm.rs │ ├── bin │ ├── ar.rs │ ├── as.rs │ ├── gcc.rs │ ├── ld.rs │ └── wrapper.rs │ ├── container.rs │ ├── env.rs │ ├── getopt.rs │ ├── lib.rs │ ├── listing.rs │ └── toolchain │ ├── ar.rs │ ├── cc1.rs │ ├── gas.rs │ ├── ld.rs │ └── mod.rs ├── tests ├── Cargo.toml └── benchmark │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── benchmark.rs │ └── main.c └── wiki ├── EVENTS.md ├── PROCESS_IMAGE ├── README.md ├── symimg.png └── symimg.svg ├── README.md └── TOOLCHAIN.md /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | .* 3 | !ewe/ 4 | !squid/ 5 | !tests/ 6 | !Cargo.toml 7 | !Cargo.lock 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "examples/musl/musl"] 2 | path = examples/musl/musl 3 | url = git://git.musl-libc.org/musl 4 | ignore = dirty 5 | [submodule "examples/readelf/binutils-gdb"] 6 | path = examples/readelf/binutils-gdb 7 | url = git://sourceware.org/git/binutils-gdb.git 8 | ignore = dirty 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["squid_ewe", "squid", "tests"] 3 | default-members = ["squid"] 4 | resolver = "2" 5 | 6 | [profile.release] 7 | lto = true 8 | codegen-units = 1 9 | panic = "abort" 10 | debug = true 11 | 12 | [workspace.package] 13 | version = "2.0.3" 14 | authors = ["Patrick D."] 15 | edition = "2021" 16 | license = "MIT" 17 | repository = "https://github.com/fkie-cad/squid" 18 | keywords = ["emulation", "fuzzing", "security"] 19 | 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest AS ewe-builder 2 | WORKDIR ewe 3 | COPY . ./ 4 | RUN cargo build -p squid_ewe --release --bins 5 | 6 | FROM archlinux:latest AS toolchain-builder 7 | ARG jobs="1" 8 | RUN pacman-key --init && \ 9 | pacman-key --populate && \ 10 | pacman -Sy --noconfirm archlinux-keyring && \ 11 | pacman -Su --noconfirm git autoconf automake curl python3 libmpc mpfr gmp gawk base-devel bison flex texinfo gperf libtool patchutils bc zlib expat 12 | WORKDIR /riscv-gnu-toolchain 13 | RUN git clone --progress https://github.com/pd-fkie/riscv-gnu-toolchain . && \ 14 | git fetch --tags && \ 15 | git checkout squid-version 16 | RUN git submodule update --init --recursive --progress ./gcc && \ 17 | git submodule update --init --recursive --progress ./glibc && \ 18 | git submodule update --init --recursive --progress ./binutils 19 | RUN autoreconf -i && \ 20 | mkdir -p /riscv && \ 21 | ./configure --prefix=/riscv --with-arch=rv64iafdm --disable-gdb --with-target-cflags="-fno-jump-tables -g" --with-target-cxxflags="-fno-jump-tables -g" && \ 22 | make -j $jobs linux 23 | 24 | FROM archlinux:latest AS squid 25 | RUN mkdir /riscv /ewe && pacman -Sy --noconfirm glibc libisl libmpc flex python guile qemu-user 26 | COPY --from=toolchain-builder /riscv /riscv/ 27 | COPY --from=ewe-builder /ewe/target/release/gcc \ 28 | /ewe/target/release/ld \ 29 | /ewe/target/release/wrapper \ 30 | /ewe/target/release/as \ 31 | /ewe/target/release/ar \ 32 | /ewe/ 33 | ENTRYPOINT /bin/bash -i 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FKIE-CAD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |
6 | squid 7 |
8 |

9 | 10 | `squid` is a RISC-V emulator with features that make it a powerful tool for vulnerability research and fuzzing. 11 | 12 | It utilizes AOT instead of JIT compilation and allows you to rewrite the binary's code before emulation. 13 | During runtime, you get full control over your target by handling all system calls and other events yourself. 14 | This makes it easy to create and combine new sanitizers and test programs for all kinds of vulnerabilities, not just memory corruptions. 15 | 16 | ## Features 17 | - Fast snapshots 18 | - Byte-level permissions on memory 19 | - Rewriting binaries before emulation 20 | - Integration into LibAFL 21 | 22 | However, it can run only single-threaded Linux user-space applications that are written in C. 23 | The source of the target _must_ be available because `squid` supports only binaries that have been compiled 24 | with this specific set of flags: 25 | ``` 26 | -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= 27 | ``` 28 | This makes `squid` unsuitable for blackbox fuzzing. Instead, it was built to augment traditional greybox fuzzing. 29 | It is encouraged to combine `squid` with native fuzzers to achieve both, high throughput and enhanced bug detection. 30 | 31 | ## Demo 32 | As a quick appetizer let's have a look at how we can overcome common restrictions of LLVM's sanitizers with `squid`. 33 | 34 | One of the biggest restrictions is that multiple sanitizers cannot be combined in a single build. 35 | Trying to invoke a compiler like this: 36 | ``` 37 | clang -fsanitize=address,memory 38 | ``` 39 | results in 40 | ``` 41 | clang: error: invalid argument '-fsanitize=address' not allowed with '-fsanitize=memory' 42 | ``` 43 | 44 | However, since `squid` allows us to do binary rewriting, we can recreate ASAN and MSAN instrumentation ourselves. 45 | We just have to compile our target with the flags mentioned above and then we can instrument and emulate it like this: 46 | ```rs 47 | fn main() { 48 | // 1) Load and lift the target binary into our custom IR 49 | let mut compiler Compiler::loader() 50 | .binary("./some-riscv-binary") // The target binary 51 | .load(); 52 | 53 | // 2) Run the ASAN pass over the binary to insert redzones 54 | // and interceptors for the heap functions similar to LLVM's 55 | // ASAN. 56 | let mut asan_pass = AsanPass::new(); 57 | compiler.run_pass(&mut asan_pass); 58 | 59 | // 3) AOT compile functions in IR to native machine code by 60 | // translating the IR to C code that is then compiled with clang 61 | let backend = ClangBackend::builder() 62 | .stack_size(2 * 1024 * 1024) 63 | .heap_size(16 * 1024 * 1024) 64 | .enable_uninit_stack(true) // MSAN ! 65 | .build(); 66 | let mut runtime = compiler.compile(backend); 67 | 68 | // 4) Run the binary, handle syscalls and interceptors 69 | loop { 70 | match runtime.run() { 71 | Ok(event) => match event { 72 | EVENT_SYSCALL => /* we have to emulate system calls ourselves here... */, 73 | EVENT_ASAN => /* ASAN's interceptors have fired */ 74 | }, 75 | Err(fault) => /* Some kind of fault occured, e.g. a segfault */, 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | This gives us support for 82 | - __ASAN__: Because the `AsanPass` inserts redzones around global variables and registers interceptors 83 | that must be handled in `runtime.run()` 84 | - __MSAN__: Because we tell the backend to mark newly created stackframes as uninitialized with `enable_uninit_stack(true)`. 85 | New heap memory returned to `malloc()` is always marked as uninitialized per default. 86 | 87 | And then, we could go even further and combine even more sanitizers to catch a broader range of vulnerabilities, not just 88 | memory corruptions. 89 | 90 | ## Getting Started 91 | You can find detailed explanations how to harness `squid` in our [wiki](https://github.com/fkie-cad/squid/tree/main/wiki). 92 | For a gentle introduction, see the [hello world](https://github.com/fkie-cad/squid/tree/main/examples/helloworld) example. 93 | For an example how to combine native and emulation-based fuzzing for maximum effectiveness, see our [readelf fuzzer](https://github.com/fkie-cad/squid/tree/main/examples/readelf). 94 | 95 | If you find that something is not properly documented / explained or you have any other questions, please 96 | do not hesitate to [create an issue](https://github.com/fkie-cad/squid/issues/new). 97 | -------------------------------------------------------------------------------- /ant/.gitignore: -------------------------------------------------------------------------------- 1 | ld-linux-riscv64-lp64d.so.1* 2 | libdl.so.2* 3 | 4 | -------------------------------------------------------------------------------- /ant/Makefile: -------------------------------------------------------------------------------- 1 | CC=/ewe/gcc 2 | CFLAGS=-g -O0 -fvisibility=hidden -fPIC -shared -nostdlib -mno-relax 3 | 4 | C_FILES=$(wildcard src/*.c) 5 | H_FILES=$(wildcard src/*.h) 6 | BINARIES=ld-linux-riscv64-lp64d.so.1 libdl.so.2 7 | 8 | all: $(BINARIES) 9 | 10 | $(BINARIES): $(C_FILES) $(H_FILES) 11 | $(CC) -o $@ $(CFLAGS) -Isrc/ $(C_FILES) 12 | 13 | .PHONY: clean, all 14 | clean: 15 | @rm -fv $(BINARIES) *.ewe 16 | -------------------------------------------------------------------------------- /ant/README.md: -------------------------------------------------------------------------------- 1 | # Ant 2 | 3 | Ant is a fake dynamic linker DSO and replaces 4 | - `ld-linux-riscv64-lp64d.so.1` 5 | - `libdl.so.2` 6 | 7 | At the moment `ant` does nothing but raise breakpoints in its functions 8 | but that may change in the future. 9 | 10 | ## Building 11 | Start the `squid-toolchain` container: 12 | ``` 13 | docker run --rm -it -v "$PWD:/io" squid-toolchain 14 | ``` 15 | 16 | Inside the container execute: 17 | ``` 18 | pacman -S make 19 | cd /io 20 | make 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /ant/src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VISIBLE __attribute__((visibility ("default"))) 4 | -------------------------------------------------------------------------------- /ant/src/ld.c: -------------------------------------------------------------------------------- 1 | 2 | #include "common.h" 3 | 4 | VISIBLE 5 | unsigned long long __stack_chk_guard = 0x1234; 6 | 7 | VISIBLE 8 | void _dl_fatal_printf (void) { 9 | __builtin_trap(); 10 | } 11 | 12 | VISIBLE 13 | void _dl_exception_create (void) { 14 | __builtin_trap(); 15 | } 16 | 17 | VISIBLE 18 | void __tunable_get_val (void) { 19 | __builtin_trap(); 20 | } 21 | 22 | VISIBLE 23 | void _dl_find_dso_for_object (void) { 24 | __builtin_trap(); 25 | } 26 | 27 | VISIBLE 28 | char _rtld_global[4120]; 29 | VISIBLE 30 | char _rtld_global_ro[304]; 31 | 32 | VISIBLE 33 | int __libc_enable_secure = 0; 34 | 35 | VISIBLE 36 | void* __libc_stack_end; 37 | 38 | VISIBLE 39 | char** _dl_argv; 40 | 41 | VISIBLE 42 | void _dl_make_stack_executable (void) { 43 | __builtin_trap(); 44 | } 45 | -------------------------------------------------------------------------------- /ant/src/tls.c: -------------------------------------------------------------------------------- 1 | 2 | #include "common.h" 3 | 4 | typedef struct { 5 | unsigned long int ti_module; 6 | unsigned long int ti_offset; 7 | } tls_index; 8 | 9 | VISIBLE 10 | void* __tls_get_addr (tls_index* ti) { 11 | (void) ti; 12 | 13 | __builtin_trap(); 14 | } 15 | 16 | VISIBLE 17 | void _dl_deallocate_tls (void) { 18 | __builtin_trap(); 19 | } 20 | 21 | VISIBLE 22 | void _dl_allocate_tls (void) { 23 | __builtin_trap(); 24 | } 25 | 26 | VISIBLE 27 | void _dl_get_tls_static_info (void) { 28 | __builtin_trap(); 29 | } 30 | 31 | VISIBLE 32 | void _dl_allocate_tls_init (void) { 33 | __builtin_trap(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/helloworld/.gitignore: -------------------------------------------------------------------------------- 1 | helloworld 2 | emu.* 3 | process_image.* 4 | -------------------------------------------------------------------------------- /examples/helloworld/Makefile: -------------------------------------------------------------------------------- 1 | CC=/riscv/bin/riscv64-unknown-linux-gnu-gcc 2 | CFLAGS=-fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= -nostdlib -Wl,-emain -Wl,-dynamic-linker,/riscv/sysroot/lib/ld-2.33.so 3 | 4 | helloworld: main.c syscall.s 5 | $(CC) -o $@ $(CFLAGS) $^ 6 | 7 | .PHONY: clean 8 | clean: 9 | @rm -fv helloworld 10 | -------------------------------------------------------------------------------- /examples/helloworld/README.md: -------------------------------------------------------------------------------- 1 | # Running a hello world program with squid 2 | 3 | This example showcases the bare minimum steps necessary to get `squid` up and running. 4 | 5 | ## Build the target 6 | Enter the `squid-toolchain` container: 7 | ``` 8 | docker run --rm -it -v "$PWD:/io" squid-toolchain 9 | ``` 10 | 11 | And execute 12 | ``` 13 | pacman -S make 14 | cd /io 15 | make 16 | ``` 17 | 18 | ## Run the emulator 19 | Simply run 20 | ``` 21 | cargo run --example helloworld 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/helloworld/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // From syscall.s 4 | int sys_write (int fd, char* buf, size_t len); 5 | int sys_exit_group (int code); 6 | 7 | int main (void) { 8 | sys_write(1, "Hello World!\n", 13); 9 | sys_exit_group(123); 10 | __builtin_unreachable(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/helloworld/run-helloworld.rs: -------------------------------------------------------------------------------- 1 | use squid::{ 2 | backends::clang::{ 3 | ClangBackend, 4 | ClangRuntime, 5 | ClangRuntimeFault, 6 | }, 7 | event::EVENT_SYSCALL, 8 | frontend::VAddr, 9 | passes::ImageDOTPass, 10 | riscv::{ 11 | register::GpRegister, 12 | syscalls, 13 | }, 14 | runtime::Runtime, 15 | Compiler, 16 | }; 17 | 18 | // Do one run of the target binary from entrypoint to exit() and forward all system calls 19 | fn execute(mut runtime: ClangRuntime) -> Result<(), ClangRuntimeFault> { 20 | loop { 21 | match runtime.run()? { 22 | EVENT_SYSCALL => { 23 | let number = runtime.get_gp_register(GpRegister::a7); 24 | 25 | match number { 26 | syscalls::write => { 27 | // Get syscall arguments 28 | let fd = runtime.get_gp_register(GpRegister::a0) as i32; 29 | let buf = runtime.get_gp_register(GpRegister::a1) as VAddr; 30 | let len = runtime.get_gp_register(GpRegister::a2) as usize; 31 | 32 | // Do the syscall 33 | let data = runtime.load_slice(buf, len)?; 34 | let ret = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, len) }; 35 | 36 | // Set syscall return value 37 | runtime.set_gp_register(GpRegister::a0, ret as u64); 38 | }, 39 | syscalls::exit_group => { 40 | let code = runtime.get_gp_register(GpRegister::a0) as i32; 41 | unsafe { 42 | libc::exit(code); 43 | } 44 | }, 45 | _ => unreachable!(), 46 | } 47 | }, 48 | _ => unreachable!(), 49 | } 50 | } 51 | } 52 | 53 | fn main() { 54 | // 1) Load the target binary 55 | let mut compiler = Compiler::loader() 56 | .binary("./helloworld") 57 | .load() 58 | .unwrap(); 59 | 60 | // 2) Run passes over binary 61 | compiler.run_pass(&mut ImageDOTPass::new("process_image.dot")).unwrap(); 62 | 63 | // 3) AOT compile code in binary for fast emulation 64 | let backend = ClangBackend::builder() 65 | .stack_size(1024 * 1024) 66 | .progname("helloworld") // argv[0] 67 | .source_file("./emu.c") // The AOT code goes into this file 68 | .cflag("-g") 69 | .cflag("-O0") 70 | .build() 71 | .unwrap(); 72 | let runtime = compiler.compile(backend).unwrap(); 73 | 74 | // 4) Emulate the binary 75 | execute(runtime).unwrap(); 76 | } 77 | -------------------------------------------------------------------------------- /examples/helloworld/syscall.s: -------------------------------------------------------------------------------- 1 | .text 2 | 3 | sys_write: 4 | li a7, 64 5 | ecall 6 | jr ra 7 | 8 | sys_exit_group: 9 | li a7, 94 10 | ecall 11 | jr ra 12 | 13 | .global sys_write 14 | .global sys_exit_group 15 | -------------------------------------------------------------------------------- /examples/musl/README.md: -------------------------------------------------------------------------------- 1 | # MUSL 2 | 3 | This example shows how to compile [musl libc](https://musl.libc.org/) with the squid-toolchain. 4 | This library shall be used as a replacement for the glibc because it not just contains less code 5 | but also is patched to support emulators with byte-level permissions. 6 | 7 | ## Download the source 8 | ``` 9 | git submodule update --init ./musl 10 | cd musl 11 | git apply ../tls.patch ../glibc-exports.patch ../libresolv.patch ../cmsg.patch ../libpthread.patch ../cancel.patch 12 | git apply ../byte-level-permissions/*.patch 13 | cd .. 14 | ``` 15 | 16 | ## Compile the code 17 | Enter the `squid-toolchain` container: 18 | ``` 19 | docker run --rm -it -v "$PWD:/io" squid-toolchain 20 | ``` 21 | 22 | Inside the container execute: 23 | ``` 24 | cd /io 25 | bash build.sh 26 | ``` 27 | 28 | ## Get the library 29 | The resulting library is available at `musl/lib/libc.so`. 30 | Rename it to `libc.so.6` to use it as a dependency of a RISC-V executable. 31 | 32 | -------------------------------------------------------------------------------- /examples/musl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export PATH="$PATH:/riscv/bin/" 5 | pacman -S --noconfirm make 6 | 7 | cd musl 8 | CC="/riscv/bin/riscv64-unknown-linux-gnu-gcc" CFLAGS="-g -O0 -fno-jump-tables -mno-relax -D__thread=" ./configure --host=riscv64-unknown-linux-gnu --with-sysroot=/riscv/sysroot --enable-debug --disable-static 9 | make 10 | 11 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/memchr.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/memchr.c b/src/string/memchr.c 2 | index 65f0d789..16ae7403 100644 3 | --- a/src/string/memchr.c 4 | +++ b/src/string/memchr.c 5 | @@ -12,16 +12,6 @@ void *memchr(const void *src, int c, size_t n) 6 | { 7 | const unsigned char *s = src; 8 | c = (unsigned char)c; 9 | -#ifdef __GNUC__ 10 | - for (; ((uintptr_t)s & ALIGN) && n && *s != c; s++, n--); 11 | - if (n && *s != c) { 12 | - typedef size_t __attribute__((__may_alias__)) word; 13 | - const word *w; 14 | - size_t k = ONES * c; 15 | - for (w = (const void *)s; n>=SS && !HASZERO(*w^k); w++, n-=SS); 16 | - s = (const void *)w; 17 | - } 18 | -#endif 19 | for (; n && *s != c; s++, n--); 20 | return n ? (void *)s : 0; 21 | } 22 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/memcpy.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/memcpy.c b/src/string/memcpy.c 2 | index 06e88742..f9c3e759 100644 3 | --- a/src/string/memcpy.c 4 | +++ b/src/string/memcpy.c 5 | @@ -7,118 +7,6 @@ void *memcpy(void *restrict dest, const void *restrict src, size_t n) 6 | unsigned char *d = dest; 7 | const unsigned char *s = src; 8 | 9 | -#ifdef __GNUC__ 10 | - 11 | -#if __BYTE_ORDER == __LITTLE_ENDIAN 12 | -#define LS >> 13 | -#define RS << 14 | -#else 15 | -#define LS << 16 | -#define RS >> 17 | -#endif 18 | - 19 | - typedef uint32_t __attribute__((__may_alias__)) u32; 20 | - uint32_t w, x; 21 | - 22 | - for (; (uintptr_t)s % 4 && n; n--) *d++ = *s++; 23 | - 24 | - if ((uintptr_t)d % 4 == 0) { 25 | - for (; n>=16; s+=16, d+=16, n-=16) { 26 | - *(u32 *)(d+0) = *(u32 *)(s+0); 27 | - *(u32 *)(d+4) = *(u32 *)(s+4); 28 | - *(u32 *)(d+8) = *(u32 *)(s+8); 29 | - *(u32 *)(d+12) = *(u32 *)(s+12); 30 | - } 31 | - if (n&8) { 32 | - *(u32 *)(d+0) = *(u32 *)(s+0); 33 | - *(u32 *)(d+4) = *(u32 *)(s+4); 34 | - d += 8; s += 8; 35 | - } 36 | - if (n&4) { 37 | - *(u32 *)(d+0) = *(u32 *)(s+0); 38 | - d += 4; s += 4; 39 | - } 40 | - if (n&2) { 41 | - *d++ = *s++; *d++ = *s++; 42 | - } 43 | - if (n&1) { 44 | - *d = *s; 45 | - } 46 | - return dest; 47 | - } 48 | - 49 | - if (n >= 32) switch ((uintptr_t)d % 4) { 50 | - case 1: 51 | - w = *(u32 *)s; 52 | - *d++ = *s++; 53 | - *d++ = *s++; 54 | - *d++ = *s++; 55 | - n -= 3; 56 | - for (; n>=17; s+=16, d+=16, n-=16) { 57 | - x = *(u32 *)(s+1); 58 | - *(u32 *)(d+0) = (w LS 24) | (x RS 8); 59 | - w = *(u32 *)(s+5); 60 | - *(u32 *)(d+4) = (x LS 24) | (w RS 8); 61 | - x = *(u32 *)(s+9); 62 | - *(u32 *)(d+8) = (w LS 24) | (x RS 8); 63 | - w = *(u32 *)(s+13); 64 | - *(u32 *)(d+12) = (x LS 24) | (w RS 8); 65 | - } 66 | - break; 67 | - case 2: 68 | - w = *(u32 *)s; 69 | - *d++ = *s++; 70 | - *d++ = *s++; 71 | - n -= 2; 72 | - for (; n>=18; s+=16, d+=16, n-=16) { 73 | - x = *(u32 *)(s+2); 74 | - *(u32 *)(d+0) = (w LS 16) | (x RS 16); 75 | - w = *(u32 *)(s+6); 76 | - *(u32 *)(d+4) = (x LS 16) | (w RS 16); 77 | - x = *(u32 *)(s+10); 78 | - *(u32 *)(d+8) = (w LS 16) | (x RS 16); 79 | - w = *(u32 *)(s+14); 80 | - *(u32 *)(d+12) = (x LS 16) | (w RS 16); 81 | - } 82 | - break; 83 | - case 3: 84 | - w = *(u32 *)s; 85 | - *d++ = *s++; 86 | - n -= 1; 87 | - for (; n>=19; s+=16, d+=16, n-=16) { 88 | - x = *(u32 *)(s+3); 89 | - *(u32 *)(d+0) = (w LS 8) | (x RS 24); 90 | - w = *(u32 *)(s+7); 91 | - *(u32 *)(d+4) = (x LS 8) | (w RS 24); 92 | - x = *(u32 *)(s+11); 93 | - *(u32 *)(d+8) = (w LS 8) | (x RS 24); 94 | - w = *(u32 *)(s+15); 95 | - *(u32 *)(d+12) = (x LS 8) | (w RS 24); 96 | - } 97 | - break; 98 | - } 99 | - if (n&16) { 100 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 101 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 102 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 103 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 104 | - } 105 | - if (n&8) { 106 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 107 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 108 | - } 109 | - if (n&4) { 110 | - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; 111 | - } 112 | - if (n&2) { 113 | - *d++ = *s++; *d++ = *s++; 114 | - } 115 | - if (n&1) { 116 | - *d = *s; 117 | - } 118 | - return dest; 119 | -#endif 120 | - 121 | for (; n; n--) *d++ = *s++; 122 | return dest; 123 | } 124 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/memmove.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/memmove.c b/src/string/memmove.c 2 | index 5dc9cdb9..72806415 100644 3 | --- a/src/string/memmove.c 4 | +++ b/src/string/memmove.c 5 | @@ -15,26 +15,8 @@ void *memmove(void *dest, const void *src, size_t n) 6 | if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n); 7 | 8 | if (d=WS; n-=WS, d+=WS, s+=WS) *(WT *)d = *(WT *)s; 16 | - } 17 | -#endif 18 | for (; n; n--) *d++ = *s++; 19 | } else { 20 | -#ifdef __GNUC__ 21 | - if ((uintptr_t)s % WS == (uintptr_t)d % WS) { 22 | - while ((uintptr_t)(d+n) % WS) { 23 | - if (!n--) return dest; 24 | - d[n] = s[n]; 25 | - } 26 | - while (n>=WS) n-=WS, *(WT *)(d+n) = *(WT *)(s+n); 27 | - } 28 | -#endif 29 | while (n) n--, d[n] = s[n]; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/memset.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/memset.c b/src/string/memset.c 2 | index 5613a148..7267fe59 100644 3 | --- a/src/string/memset.c 4 | +++ b/src/string/memset.c 5 | @@ -33,7 +33,7 @@ void *memset(void *dest, int c, size_t n) 6 | n -= k; 7 | n &= -4; 8 | 9 | -#ifdef __GNUC__ 10 | +#if 0 11 | typedef uint32_t __attribute__((__may_alias__)) u32; 12 | typedef uint64_t __attribute__((__may_alias__)) u64; 13 | 14 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/stpcpy.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/stpcpy.c b/src/string/stpcpy.c 2 | index 4db46a9e..ca9b385f 100644 3 | --- a/src/string/stpcpy.c 4 | +++ b/src/string/stpcpy.c 5 | @@ -9,18 +9,6 @@ 6 | 7 | char *__stpcpy(char *restrict d, const char *restrict s) 8 | { 9 | -#ifdef __GNUC__ 10 | - typedef size_t __attribute__((__may_alias__)) word; 11 | - word *wd; 12 | - const word *ws; 13 | - if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { 14 | - for (; (uintptr_t)s % ALIGN; s++, d++) 15 | - if (!(*d=*s)) return d; 16 | - wd=(void *)d; ws=(const void *)s; 17 | - for (; !HASZERO(*ws); *wd++ = *ws++); 18 | - d=(void *)wd; s=(const void *)ws; 19 | - } 20 | -#endif 21 | for (; (*d=*s); s++, d++); 22 | 23 | return d; 24 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/strchr.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/strchrnul.c b/src/string/strchrnul.c 2 | index 39e2635b..3ea8d50f 100644 3 | --- a/src/string/strchrnul.c 4 | +++ b/src/string/strchrnul.c 5 | @@ -12,15 +12,6 @@ char *__strchrnul(const char *s, int c) 6 | c = (unsigned char)c; 7 | if (!c) return (char *)s + strlen(s); 8 | 9 | -#ifdef __GNUC__ 10 | - typedef size_t __attribute__((__may_alias__)) word; 11 | - const word *w; 12 | - for (; (uintptr_t)s % ALIGN; s++) 13 | - if (!*s || *(unsigned char *)s == c) return (char *)s; 14 | - size_t k = ONES * c; 15 | - for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); 16 | - s = (void *)w; 17 | -#endif 18 | for (; *s && *(unsigned char *)s != c; s++); 19 | return (char *)s; 20 | } 21 | -------------------------------------------------------------------------------- /examples/musl/byte-level-permissions/strlen.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/string/strlen.c b/src/string/strlen.c 2 | index 309990f0..4edce373 100644 3 | --- a/src/string/strlen.c 4 | +++ b/src/string/strlen.c 5 | @@ -10,13 +10,6 @@ 6 | size_t strlen(const char *s) 7 | { 8 | const char *a = s; 9 | -#ifdef __GNUC__ 10 | - typedef size_t __attribute__((__may_alias__)) word; 11 | - const word *w; 12 | - for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a; 13 | - for (w = (const void *)s; !HASZERO(*w); w++); 14 | - s = (const void *)w; 15 | -#endif 16 | for (; *s; s++); 17 | return s-a; 18 | } 19 | -------------------------------------------------------------------------------- /examples/musl/cancel.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/thread/pthread_cancel.c b/src/thread/pthread_cancel.c 2 | index 139a6fc8..19924c6e 100644 3 | --- a/src/thread/pthread_cancel.c 4 | +++ b/src/thread/pthread_cancel.c 5 | @@ -22,6 +22,7 @@ long __syscall_cp_c(syscall_arg_t nr, 6 | syscall_arg_t u, syscall_arg_t v, syscall_arg_t w, 7 | syscall_arg_t x, syscall_arg_t y, syscall_arg_t z) 8 | { 9 | + return __syscall(nr, u, v, w, x, y, z); 10 | pthread_t self; 11 | long r; 12 | int st; 13 | -------------------------------------------------------------------------------- /examples/musl/cmsg.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/network/recvmsg.c b/src/network/recvmsg.c 2 | index 03641625..d9512f64 100644 3 | --- a/src/network/recvmsg.c 4 | +++ b/src/network/recvmsg.c 5 | @@ -66,3 +66,8 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) 6 | #endif 7 | return r; 8 | } 9 | + 10 | + 11 | +struct cmsghdr *__cmsg_nxthdr (struct msghdr *mhdr, struct cmsghdr *cmsg) { 12 | + return CMSG_NXTHDR(mhdr, cmsg); 13 | +} 14 | -------------------------------------------------------------------------------- /examples/musl/glibc-exports.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/stdio/fopen.c b/src/stdio/fopen.c 2 | index 80bc341e..afcf9afb 100644 3 | --- a/src/stdio/fopen.c 4 | +++ b/src/stdio/fopen.c 5 | @@ -29,3 +29,5 @@ FILE *fopen(const char *restrict filename, const char *restrict mode) 6 | __syscall(SYS_close, fd); 7 | return 0; 8 | } 9 | + 10 | +weak_alias(fopen, fopen64); 11 | diff --git a/src/stdio/fseek.c b/src/stdio/fseek.c 12 | index c7425802..97a680a9 100644 13 | --- a/src/stdio/fseek.c 14 | +++ b/src/stdio/fseek.c 15 | @@ -46,3 +46,4 @@ int fseek(FILE *f, long off, int whence) 16 | } 17 | 18 | weak_alias(__fseeko, fseeko); 19 | +weak_alias(__fseeko, fseeko64); 20 | diff --git a/src/stdio/ftell.c b/src/stdio/ftell.c 21 | index 1e1a08d8..9009e776 100644 22 | --- a/src/stdio/ftell.c 23 | +++ b/src/stdio/ftell.c 24 | @@ -37,3 +37,4 @@ long ftell(FILE *f) 25 | } 26 | 27 | weak_alias(__ftello, ftello); 28 | +weak_alias(__ftello, ftello64); 29 | -------------------------------------------------------------------------------- /examples/musl/libpthread.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/sched/sched_yield.c b/src/sched/sched_yield.c 2 | index ee6f0e7f..7c6842d4 100644 3 | --- a/src/sched/sched_yield.c 4 | +++ b/src/sched/sched_yield.c 5 | @@ -5,3 +5,5 @@ int sched_yield() 6 | { 7 | return syscall(SYS_sched_yield); 8 | } 9 | + 10 | +weak_alias(sched_yield, pthread_yield); 11 | -------------------------------------------------------------------------------- /examples/musl/libresolv.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/include/resolv.h b/src/include/resolv.h 2 | index 945e89e6..a66669b2 100644 3 | --- a/src/include/resolv.h 4 | +++ b/src/include/resolv.h 5 | @@ -3,10 +3,10 @@ 6 | 7 | #include "../../include/resolv.h" 8 | 9 | -hidden int __dn_expand(const unsigned char *, const unsigned char *, const unsigned char *, char *, int); 10 | +int __dn_expand(const unsigned char *, const unsigned char *, const unsigned char *, char *, int); 11 | 12 | -hidden int __res_mkquery(int, const char *, int, int, const unsigned char *, int, const unsigned char*, unsigned char *, int); 13 | -hidden int __res_send(const unsigned char *, int, unsigned char *, int); 14 | -hidden int __res_msend(int, const unsigned char *const *, const int *, unsigned char *const *, int *, int); 15 | +int __res_mkquery(int, const char *, int, int, const unsigned char *, int, const unsigned char*, unsigned char *, int); 16 | +int __res_send(const unsigned char *, int, unsigned char *, int); 17 | +int __res_msend(int, const unsigned char *const *, const int *, unsigned char *const *, int *, int); 18 | 19 | #endif 20 | diff --git a/src/network/res_init.c b/src/network/res_init.c 21 | index 5dba9dfc..2355b5f0 100644 22 | --- a/src/network/res_init.c 23 | +++ b/src/network/res_init.c 24 | @@ -4,3 +4,5 @@ int res_init() 25 | { 26 | return 0; 27 | } 28 | + 29 | +weak_alias(res_init, __res_init); 30 | diff --git a/src/network/res_query.c b/src/network/res_query.c 31 | index 506dc231..21d98307 100644 32 | --- a/src/network/res_query.c 33 | +++ b/src/network/res_query.c 34 | @@ -24,3 +24,4 @@ int res_query(const char *name, int class, int type, unsigned char *dest, int le 35 | } 36 | 37 | weak_alias(res_query, res_search); 38 | +weak_alias(res_query, __res_search); 39 | -------------------------------------------------------------------------------- /examples/musl/tls.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ldso/dynlink.c b/ldso/dynlink.c 2 | index ceca3c98..9f1fa68d 100644 3 | --- a/ldso/dynlink.c 4 | +++ b/ldso/dynlink.c 5 | @@ -1581,7 +1581,7 @@ static void do_init_fini(struct dso **queue) 6 | pthread_t self = __pthread_self(); 7 | 8 | pthread_mutex_lock(&init_fini_lock); 9 | - for (i=0; (p=queue[i]); i++) { 10 | + for (i=0; queue && (p=queue[i]); i++) { 11 | while ((p->ctor_visitor && p->ctor_visitor!=self) || shutting_down) 12 | pthread_cond_wait(&ctor_cond, &init_fini_lock); 13 | if (p->ctor_visitor || p->constructed) 14 | @@ -1630,6 +1630,17 @@ weak_alias(dl_debug_state, _dl_debug_state); 15 | 16 | void __init_tls(size_t *auxv) 17 | { 18 | + /** /!\ CUSTOM IMPLEMENTATION FOR SQUID /!\ **/ 19 | + 20 | + (void) auxv; 21 | + 22 | + libc.tls_align = MIN_TLS_ALIGN; 23 | + libc.tls_size = sizeof builtin_tls; 24 | + libc.tls_cnt = 0; 25 | + libc.tls_head = NULL; 26 | + 27 | + if (__init_tp(__copy_tls((unsigned char*) builtin_tls)) < 0) 28 | + a_crash(); 29 | } 30 | 31 | static void update_tls_size() 32 | @@ -1794,6 +1805,7 @@ void __dls2b(size_t *sp, size_t *auxv) 33 | * process dependencies and relocations for the main application and 34 | * transfer control to its entry point. */ 35 | 36 | +__attribute__((__noreturn__)) 37 | void __dls3(size_t *sp, size_t *auxv) 38 | { 39 | static struct dso app, vdso; 40 | @@ -2071,7 +2083,7 @@ void __dls3(size_t *sp, size_t *auxv) 41 | errno = 0; 42 | 43 | CRTJMP((void *)aux[AT_ENTRY], argv-1); 44 | - for(;;); 45 | + __builtin_unreachable(); 46 | } 47 | 48 | static void prepare_lazy(struct dso *p) 49 | -------------------------------------------------------------------------------- /examples/readelf/.gitignore: -------------------------------------------------------------------------------- 1 | binaries/ 2 | output/ 3 | corpus/ 4 | .cur_input_* 5 | -------------------------------------------------------------------------------- /examples/readelf/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing readelf with squid 2 | 3 | This example shows how to build a readelf fuzzer with `squid` and LibAFL that combines 4 | native and emulation-based fuzzing for maximum performance. 5 | 6 | ## Download the source 7 | ``` 8 | git submodule update --init ./binutils-gdb 9 | cd binutils-gdb 10 | git apply ../binutils.patch 11 | cd .. 12 | ``` 13 | 14 | ## Compile the source 15 | Enter the `squid-toolchain` container: 16 | ``` 17 | docker run --rm -it -v "$PWD:/io" squid-toolchain 18 | ``` 19 | 20 | Inside the container execute: 21 | ``` 22 | pacman -Sy make texinfo bison diffutils gcc 23 | cd /io 24 | bash build.sh 25 | ``` 26 | 27 | This creates our fuzz target at `binutils-gdb/binutils/readelf`. 28 | 29 | ## Prepare the fuzzer 30 | Create a folder called `binaries`, where we are gonna put the RISC-V binaries `readelf` and its dependencies `libdl.so.2` and `libc.so.6`. 31 | `libdl.so.2` may come from [ant](../../ant) and `libc.so.6` may be the [musl libc](../musl). 32 | ``` 33 | mkdir binaries 34 | cp binutils-gdb/binutils/readelf ./binaries 35 | cp ../musl/musl/lib/libc.so ./binaries/libc.so.6 36 | cp ../../ant/libdl.so.2 ./binaries 37 | ``` 38 | 39 | Then, we create a corpus: 40 | ``` 41 | mkdir corpus 42 | echo -en "\x00" > corpus/0 43 | ``` 44 | 45 | Finally, we are gonna prepare a native `readelf` binary compiled with `afl-clang-lto` from the `binutil-gdb` source tree: 46 | ```sh 47 | cd binutils-gdb 48 | CC="$AFL_PATH/afl-clang-lto" CFLAGS="-flto -fomit-frame-pointer -Ofast" LDFLAGS="-flto" ./configure 49 | make 50 | ``` 51 | 52 | ## Run the fuzzer 53 | We are gonna spawn two instances of the fuzzer. A speedy instance with the `afl-clang-lto` compiled binary and 54 | a slow instance with `squid` and advanced crash oracles. 55 | 56 | ``` 57 | cargo run --release --example readelf_fuzzer -- fuzz \ 58 | --riscv-binaries ./binaries \ 59 | --corpus ./corpus \ 60 | --output ./output \ 61 | --cores 0-1 \ 62 | --native-binary binutils-gdb/binutils/readelf 63 | ``` 64 | 65 | ## Performance measurements 66 | The following stats were collected executing `readelf -a` over and over again with the same input from the corpus 67 | on an `Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz` cpu. 68 | 69 | Empty file (1 byte): 70 | - squid: 31k exec/s with ~700M instr/s 71 | - native: 3k exec/s 72 | 73 | Tiny, valid ELF file (7952 bytes): 74 | - squid: 815 exec/s with ~913M instr/s 75 | - native: 2.1k exec/s 76 | 77 | Medium, valid ELF file (68024 bytes): 78 | - squid: 215 exec/s with ~870M instr/s 79 | - native: 1250 exec/s 80 | 81 | Big, valid ELF file (2292568 bytes): 82 | - squid: 25 exec/s with ~900M instr/s 83 | - native: 1150 exec/s 84 | -------------------------------------------------------------------------------- /examples/readelf/binutils.patch: -------------------------------------------------------------------------------- 1 | diff --git a/binutils/dwarf.c b/binutils/dwarf.c 2 | index 4f695bf2bca..bb917e9ddbd 100644 3 | --- a/binutils/dwarf.c 4 | +++ b/binutils/dwarf.c 5 | @@ -4491,7 +4491,7 @@ display_debug_lines_raw (struct dwarf_section * section, 6 | while (data < end) 7 | { 8 | static DWARF2_Internal_LineInfo saved_linfo; 9 | - DWARF2_Internal_LineInfo linfo; 10 | + DWARF2_Internal_LineInfo linfo = {0}; 11 | unsigned char *standard_opcodes; 12 | unsigned char *end_of_sequence; 13 | int i; 14 | diff --git a/binutils/readelf.c b/binutils/readelf.c 15 | index 97d72d0b95f..ae82adf0841 100644 16 | --- a/binutils/readelf.c 17 | +++ b/binutils/readelf.c 18 | @@ -368,7 +368,7 @@ enum versioned_symbol_info 19 | static int 20 | fseek64 (FILE *stream, int64_t offset, int whence) 21 | { 22 | -#if defined (HAVE_FSEEKO64) 23 | +#if 0 24 | off64_t o = offset; 25 | if (o != offset) 26 | { 27 | @@ -376,7 +376,7 @@ fseek64 (FILE *stream, int64_t offset, int whence) 28 | return -1; 29 | } 30 | return fseeko64 (stream, o, whence); 31 | -#elif defined (HAVE_FSEEKO) 32 | +#elif 0 33 | off_t o = offset; 34 | if (o != offset) 35 | { 36 | diff --git a/libiberty/xmalloc.c b/libiberty/xmalloc.c 37 | index c30b8966ada..bdcf52d20fa 100644 38 | --- a/libiberty/xmalloc.c 39 | +++ b/libiberty/xmalloc.c 40 | @@ -119,7 +119,7 @@ xmalloc_set_program_name (const char *s) 41 | void 42 | xmalloc_failed (size_t size) 43 | { 44 | -#ifdef HAVE_SBRK 45 | +#if 0 46 | size_t allocated; 47 | 48 | if (first_break != NULL) 49 | -------------------------------------------------------------------------------- /examples/readelf/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | export PATH="/riscv/bin/:$PATH" 5 | 6 | cd binutils-gdb/zlib 7 | CFLAGS="-O0 -g -fPIC -fno-jump-tables -mno-relax" ./configure --host=riscv64-unknown-linux-gnu --without-system-zlib --disable-shared 8 | make 9 | 10 | cd ../libsframe 11 | CFLAGS="-O0 -g -fPIC -fno-jump-tables -mno-relax" ./configure --host=riscv64-unknown-linux-gnu --disable-shared 12 | make 13 | 14 | cd ../bfd 15 | CFLAGS="-O0 -g -fPIC -fno-jump-tables -mno-relax" ./configure --host=riscv64-unknown-linux-gnu --disable-nls --without-zstd --without-system-zlib --disable-shared 16 | make 17 | 18 | cd ../libiberty 19 | CFLAGS="-O0 -g -fPIC -fno-jump-tables -mno-relax" ./configure --host=riscv64-unknown-linux-gnu --disable-shared 20 | make 21 | 22 | cd ../opcodes/ 23 | CFLAGS="-O0 -g -fPIC -fno-jump-tables -mno-relax" ./configure --host=riscv64-unknown-linux-gnu --disable-shared 24 | make 25 | 26 | cd ../binutils 27 | CFLAGS="-O0 -g -fPIE -fno-jump-tables -mno-relax" LDFLAGS="-pie" ./configure --host=riscv64-unknown-linux-gnu --disable-libctf --disable-nls --without-zstd --disable-shared --without-system-zlib 28 | make readelf 29 | 30 | -------------------------------------------------------------------------------- /examples/readme-demo/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | libc.so.6* 3 | aot.* 4 | -------------------------------------------------------------------------------- /examples/readme-demo/README.md: -------------------------------------------------------------------------------- 1 | # README demo 2 | 3 | This example contains the full code for the demo in the [README](../../README.md#demo). 4 | To run the demo yourself you must first build the test program and then compile the harness. 5 | 6 | ## Building the target 7 | Enter the `squid-toolchain` container 8 | ``` 9 | docker run -it --rm -v $PWD:/io squid-toolchain 10 | ``` 11 | Inside the container execute 12 | ``` 13 | cd /io 14 | /riscv/bin/riscv64-unknown-linux-gnu-gcc -o test -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= test.c 15 | ``` 16 | 17 | Also, you need a `libc.so.6`, preferably [musl](../musl). 18 | 19 | ## Running the demo 20 | Execute 21 | ``` 22 | cargo run --example readme-demo --release -- 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/readme-demo/demo.rs: -------------------------------------------------------------------------------- 1 | use squid::{ 2 | backends::clang::{ 3 | perms::PERM_UNINIT, 4 | ClangBackend, 5 | ClangRuntime, 6 | ClangRuntimeFault, 7 | }, 8 | event::{ 9 | EVENT_BREAKPOINT, 10 | EVENT_SYSCALL, 11 | }, 12 | frontend::VAddr, 13 | passes::AsanPass, 14 | riscv::{ 15 | register::GpRegister, 16 | syscalls, 17 | }, 18 | runtime::Runtime, 19 | Compiler, 20 | }; 21 | 22 | fn handle_asan_event(pass: &AsanPass, event: usize, runtime: &mut ClangRuntime) -> Result<(), ClangRuntimeFault> { 23 | let event = Some(event); 24 | 25 | if event == pass.malloc_event().map(|x| x.id()) { 26 | let size = runtime.get_gp_register(GpRegister::a0) as usize; 27 | let addr = runtime.dynstore_allocate(size)?; 28 | runtime.set_gp_register(GpRegister::a0, addr); 29 | } else if event == pass.free_event().map(|x| x.id()) { 30 | let addr = runtime.get_gp_register(GpRegister::a0); 31 | runtime.dynstore_deallocate(addr)?; 32 | } else if event == pass.realloc_event().map(|x| x.id()) { 33 | let chunk = runtime.get_gp_register(GpRegister::a0) as VAddr; 34 | let size = runtime.get_gp_register(GpRegister::a1) as usize; 35 | 36 | if size == 0 { 37 | runtime.dynstore_deallocate(chunk)?; 38 | runtime.set_gp_register(GpRegister::a0, 0); 39 | } else if chunk == 0 { 40 | let addr = runtime.dynstore_allocate(size)?; 41 | runtime.set_gp_register(GpRegister::a0, addr); 42 | } else { 43 | let new_chunk = runtime.dynstore_reallocate(chunk, size)?; 44 | runtime.set_gp_register(GpRegister::a0, new_chunk); 45 | } 46 | } else if event == pass.calloc_event().map(|x| x.id()) { 47 | let a = runtime.get_gp_register(GpRegister::a0) as usize; 48 | let b = runtime.get_gp_register(GpRegister::a1) as usize; 49 | let size = a 50 | .checked_mul(b) 51 | .ok_or_else(|| ClangRuntimeFault::InternalError(format!("calloc overflow: {} * {}", a, b)))?; 52 | let addr = runtime.dynstore_allocate(size)?; 53 | for perm in runtime.permissions_mut(addr, size)? { 54 | *perm &= !PERM_UNINIT; 55 | } 56 | runtime.set_gp_register(GpRegister::a0, addr); 57 | } else { 58 | unreachable!() 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | fn forward_syscall(runtime: &mut ClangRuntime) -> Result<(), ClangRuntimeFault> { 65 | let number = runtime.get_gp_register(GpRegister::a7); 66 | 67 | match number { 68 | syscalls::exit_group => { 69 | let code = runtime.get_gp_register(GpRegister::a0) as i32; 70 | unsafe { 71 | libc::exit(code); 72 | } 73 | }, 74 | syscalls::set_tid_address => { 75 | // Don't forward sice this syscall is not important 76 | runtime.set_gp_register(GpRegister::a0, 1); 77 | }, 78 | syscalls::ioctl => { 79 | let cmd = runtime.get_gp_register(GpRegister::a1); 80 | 81 | match cmd { 82 | libc::TIOCGWINSZ => { 83 | runtime.set_gp_register(GpRegister::a0, 0); 84 | }, 85 | _ => todo!("ioctl {} is not implemented yet", cmd), 86 | } 87 | }, 88 | syscalls::writev => { 89 | let fd = runtime.get_gp_register(GpRegister::a0) as i32; 90 | let iov = runtime.get_gp_register(GpRegister::a1) as VAddr; 91 | let iovcnt = runtime.get_gp_register(GpRegister::a2); 92 | let mut ret = 0; 93 | 94 | for i in 0..iovcnt { 95 | let iov = iov + i * 16; 96 | let iov_base = runtime.load_dword(iov)? as VAddr; 97 | let iov_len = runtime.load_dword(iov + 8)? as usize; 98 | let data = runtime.load_slice(iov_base, iov_len)?; 99 | ret += unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, iov_len) }; 100 | } 101 | 102 | runtime.set_gp_register(GpRegister::a0, ret as u64); 103 | }, 104 | syscalls::write => { 105 | let fd = runtime.get_gp_register(GpRegister::a0) as i32; 106 | let buf = runtime.get_gp_register(GpRegister::a1) as VAddr; 107 | let len = runtime.get_gp_register(GpRegister::a2) as usize; 108 | let data = runtime.load_slice(buf, len)?; 109 | let ret = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, len) }; 110 | runtime.set_gp_register(GpRegister::a0, ret as u64); 111 | }, 112 | _ => todo!("Syscall {} is not implemented yet", number), 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | fn display_crash_report(fault: ClangRuntimeFault, runtime: ClangRuntime) -> ! { 119 | let raw_code = runtime.raw_return_code(); 120 | let last_instr = runtime.get_last_instruction(); 121 | 122 | println!("====================== CRASH REPORT ================================"); 123 | println!("ERROR: {:?} at pc={:#x}", raw_code, last_instr); 124 | println!("{:?}", fault); 125 | println!("===================================================================="); 126 | 127 | std::process::exit(127); 128 | } 129 | 130 | fn main() { 131 | // 1) Load and lift the target binary into our custom IR 132 | let mut compiler = Compiler::loader() 133 | .binary("./test") 134 | .search_path(".") 135 | .load() 136 | .unwrap(); 137 | 138 | // 2) Run the ASAN pass over the binary to insert redzones and interceptors for the heap functions 139 | let mut asan_pass = AsanPass::new(); 140 | compiler.run_pass(&mut asan_pass).unwrap(); 141 | 142 | // 3) AOT compile functions in IR down to native machine code by generating C code that we compile with clang 143 | let arg = std::env::args().nth(1).unwrap(); 144 | let backend = ClangBackend::builder() 145 | .stack_size(2 * 1024 * 1024) 146 | .heap_size(16 * 1024 * 1024) 147 | .enable_uninit_stack(true) // MemorySanitizer 148 | .progname("test") // argv[0] 149 | .arg(arg) // argv[1] 150 | .source_file("./aot.c") // The AOT code goes into this file 151 | .update_last_instruction(true) 152 | .build() 153 | .unwrap(); 154 | let mut runtime = compiler.compile(backend).unwrap(); 155 | 156 | // 4) Emulate the binary and handle all runtime events 157 | println!("Running..."); 158 | 159 | loop { 160 | match runtime.run() { 161 | Ok(event) => match event { 162 | EVENT_BREAKPOINT => panic!("Hit a breakpoint"), 163 | EVENT_SYSCALL => forward_syscall(&mut runtime).unwrap(), 164 | _ => handle_asan_event(&asan_pass, event, &mut runtime).unwrap(), 165 | }, 166 | Err(fault) => display_crash_report(fault, runtime), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/readme-demo/test.c: -------------------------------------------------------------------------------- 1 | // /riscv/bin/riscv64-unknown-linux-gnu-gcc -o test -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= test.c 2 | 3 | #include 4 | #include 5 | 6 | int main (int argc, char** argv) { 7 | if (argc < 2) { 8 | printf("USAGE: %s \n", argv[0]); 9 | return 1; 10 | } 11 | 12 | int* array = malloc(16 * sizeof(int)); 13 | int index = atoi(argv[1]); 14 | 15 | // Partially initialize array 16 | array[0] = 123; 17 | array[1] = 456; 18 | array[2] = 789; 19 | 20 | printf("array[%d] = %d\n", index, array[index]); 21 | 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/squid/a081a984f2d54362498f332496c93113858db514/logo.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | use_small_heuristics = "Max" 3 | edition = "2021" 4 | match_block_trailing_comma = true 5 | use_field_init_shorthand = true 6 | struct_lit_width = 0 7 | imports_granularity = "Crate" 8 | group_imports = "StdExternalCrate" 9 | imports_layout = "Vertical" 10 | -------------------------------------------------------------------------------- /squid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squid" 3 | description = "A RISC-V emulator with AOT compilation for fuzzing" 4 | readme = "../README.md" 5 | version.workspace = true 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | repository.workspace = true 10 | keywords.workspace = true 11 | 12 | [dependencies] 13 | squid_ewe = { path = "../squid_ewe", version = "2.0.1" } 14 | goblin = { version = "0.7", default-features = false, features = ["std", "elf32", "elf64", "endian_fd"]} 15 | memmap2 = "0.7" 16 | indicatif = "0.17" 17 | colored = "2.0" 18 | thiserror = "1.0" 19 | num-traits = "0.2" 20 | paste = "1.0" 21 | rustc-hash = "1.1" 22 | libc = "0.2" 23 | libloading = "0.8" 24 | ahash = "0.8" 25 | 26 | [dev-dependencies] 27 | rand = "0.8" 28 | libafl = "0.13" 29 | libafl_bolts = "0.13" 30 | clap = { version = "4.4", features = ["derive"] } 31 | serde = "1.0" 32 | mimalloc = { version = "0.1", default-features = false } 33 | 34 | [features] 35 | default = ["tui"] 36 | 37 | # Enables animations in the terminal 38 | tui = [] 39 | 40 | [[example]] 41 | name = "readelf_fuzzer" 42 | path = "../examples/readelf/readelf-fuzzer.rs" 43 | 44 | [[example]] 45 | name = "helloworld" 46 | path = "../examples/helloworld/run-helloworld.rs" 47 | 48 | [[example]] 49 | name = "readme-demo" 50 | path = "../examples/readme-demo/demo.rs" 51 | 52 | [[example]] 53 | name = "exim_fuzzer" 54 | path = "../examples/exim/exim-fuzzer.rs" 55 | -------------------------------------------------------------------------------- /squid/src/backends/backend.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | event::EventPool, 3 | frontend::ProcessImage, 4 | runtime::Runtime, 5 | Logger, 6 | }; 7 | 8 | /// Any type that implements this trait can be used as a "backend". 9 | /// A backend is responsible for lowering `squid`s IR to native machine code 10 | /// and making the compiled code available to the user in the form of a [`Runtime`]. 11 | pub trait Backend { 12 | /// This is the type of the Runtime created by the backend. 13 | /// Each backend has its own corresponding runtime. 14 | type Runtime: Runtime; 15 | 16 | /// This error type can be returned by the backend during its operations. 17 | type Error: std::error::Error; 18 | 19 | /// The name of the backend (displayed on the terminal) 20 | fn name(&self) -> String; 21 | 22 | /// This function realizes the main functionality of a backend. 23 | /// It receives the process image and the event pool and constructs a runtime 24 | /// that enables a user to execute the code in the process image. 25 | fn create_runtime( 26 | &mut self, 27 | image: ProcessImage, 28 | event_pool: EventPool, 29 | logger: &Logger, 30 | ) -> Result; 31 | } 32 | -------------------------------------------------------------------------------- /squid/src/backends/clang/address.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::frontend::{ 4 | ChunkContent, 5 | HasId, 6 | ProcessImage, 7 | VAddr, 8 | }; 9 | 10 | pub(crate) const POINTER_TAG_SHIFT: u32 = 63; 11 | pub(crate) const POINTER_TAG_MASK: VAddr = 0x8000000000000000; 12 | pub(crate) const POINTER_TAG_DATA: VAddr = 0; 13 | pub(crate) const POINTER_TAG_CODE: VAddr = 0x8000000000000000; 14 | pub(crate) const POINTER_CODE_MASK: VAddr = 0x7FFFFFF000000000; 15 | pub(crate) const POINTER_CODE_SHIFT: u32 = 36; 16 | 17 | pub(crate) enum AddressSpace { 18 | Data(usize), 19 | Code(usize), 20 | } 21 | 22 | impl AddressSpace { 23 | pub(crate) fn decode(addr: VAddr) -> Self { 24 | match addr & POINTER_TAG_MASK { 25 | POINTER_TAG_DATA => Self::Data((addr & !POINTER_TAG_MASK) as usize), 26 | POINTER_TAG_CODE => Self::Code((addr & !POINTER_TAG_MASK) as usize), 27 | _ => unreachable!(), 28 | } 29 | } 30 | 31 | pub(crate) fn encode(&self) -> VAddr { 32 | match self { 33 | Self::Data(offset) => { 34 | let offset = *offset as VAddr; 35 | assert_eq!(offset & POINTER_TAG_MASK, 0); 36 | POINTER_TAG_DATA | offset 37 | }, 38 | Self::Code(index) => { 39 | let index = *index as VAddr; 40 | assert_eq!(index & POINTER_TAG_MASK, 0); 41 | POINTER_TAG_CODE | index 42 | }, 43 | } 44 | } 45 | } 46 | 47 | #[inline] 48 | fn make_code_address(idx: usize) -> VAddr { 49 | AddressSpace::Code(idx << POINTER_CODE_SHIFT).encode() 50 | } 51 | 52 | pub(crate) struct AddressLayouter { 53 | globals_size: usize, 54 | code_size: usize, 55 | } 56 | 57 | impl AddressLayouter { 58 | pub(crate) fn new() -> Self { 59 | Self { 60 | globals_size: 0, 61 | code_size: 0, 62 | } 63 | } 64 | 65 | fn layout_data(&mut self, image: &mut ProcessImage) { 66 | let mut cursor = 0; 67 | 68 | for elf in image.iter_elfs_mut() { 69 | for section in elf.iter_sections_mut() { 70 | if section.perms().is_executable() { 71 | continue; 72 | } 73 | 74 | section.set_vaddr(AddressSpace::Data(cursor).encode()); 75 | 76 | for symbol in section.iter_symbols_mut() { 77 | symbol.set_vaddr(AddressSpace::Data(cursor).encode()); 78 | 79 | for chunk in symbol.iter_chunks_mut() { 80 | chunk.set_vaddr(AddressSpace::Data(cursor).encode()); 81 | cursor += chunk.size(); 82 | } 83 | } 84 | } 85 | } 86 | 87 | self.globals_size = cursor; 88 | } 89 | 90 | fn layout_code(&mut self, image: &mut ProcessImage) { 91 | let mut cursor = 0; 92 | 93 | for elf in image.iter_elfs_mut() { 94 | for section in elf.iter_sections_mut() { 95 | if !section.perms().is_executable() { 96 | continue; 97 | } 98 | 99 | let section_start = cursor; 100 | section.set_vaddr(make_code_address(section_start)); 101 | 102 | for symbol in section.iter_symbols_mut() { 103 | let mut public_names = HashMap::new(); 104 | for name in symbol.public_names() { 105 | let vaddr = symbol.public_name(name).unwrap(); 106 | 107 | for chunk in symbol.iter_chunks() { 108 | if chunk.contains_address(vaddr) { 109 | public_names.insert(name.clone(), chunk.id()); 110 | break; 111 | } 112 | } 113 | } 114 | 115 | let mut private_names = HashMap::new(); 116 | for name in symbol.private_names() { 117 | let vaddr = symbol.private_name(name).unwrap(); 118 | 119 | for chunk in symbol.iter_chunks() { 120 | if chunk.contains_address(vaddr) { 121 | private_names.insert(name.clone(), chunk.id()); 122 | break; 123 | } 124 | } 125 | } 126 | 127 | let symbol_start = cursor; 128 | symbol.set_vaddr(make_code_address(symbol_start)); 129 | 130 | for chunk in symbol.iter_chunks_mut() { 131 | let chunk_start = cursor; 132 | chunk.set_vaddr(make_code_address(chunk_start)); 133 | 134 | let ChunkContent::Code(func) = chunk.content_mut() else { unreachable!() }; 135 | 136 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 137 | bb.set_vaddr(make_code_address(cursor)); 138 | cursor += 1; 139 | } 140 | 141 | chunk.set_size(cursor - chunk_start); 142 | } 143 | 144 | for (name, chunk_id) in public_names { 145 | let chunk = symbol.chunk(chunk_id).unwrap(); 146 | symbol.set_public_name(name, chunk.vaddr()); 147 | } 148 | 149 | for (name, chunk_id) in private_names { 150 | let chunk = symbol.chunk(chunk_id).unwrap(); 151 | symbol.set_private_name(name, chunk.vaddr()); 152 | } 153 | 154 | symbol.set_size(cursor - symbol_start); 155 | } 156 | 157 | section.set_size(cursor - section_start); 158 | } 159 | } 160 | 161 | self.code_size = cursor; 162 | } 163 | 164 | pub(crate) fn layout(&mut self, image: &mut ProcessImage) { 165 | self.layout_data(image); 166 | self.layout_code(image); 167 | } 168 | 169 | pub(crate) fn globals_size(&self) -> usize { 170 | self.globals_size 171 | } 172 | 173 | pub(crate) fn code_size(&self) -> usize { 174 | self.code_size 175 | } 176 | } 177 | 178 | pub(crate) fn get_entrypoint_address(image: &ProcessImage) -> VAddr { 179 | let pointer = image.entrypoint(); 180 | 181 | let chunk = image 182 | .elf(pointer.elf) 183 | .unwrap() 184 | .section(pointer.section) 185 | .unwrap() 186 | .symbol(pointer.symbol) 187 | .unwrap() 188 | .chunk(pointer.chunk) 189 | .unwrap(); 190 | 191 | let ChunkContent::Code(func) = chunk.content() else { unreachable!() }; 192 | let entry = func.cfg().entry(); 193 | 194 | func.cfg().basic_block(entry).unwrap().vaddr().unwrap() 195 | } 196 | -------------------------------------------------------------------------------- /squid/src/backends/clang/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | mod lifter; 2 | mod subcfg; 3 | 4 | pub use lifter::*; 5 | -------------------------------------------------------------------------------- /squid/src/backends/clang/codegen/subcfg.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use crate::frontend::{ 4 | ao::{ 5 | Edge, 6 | Function, 7 | Op, 8 | CFG, 9 | }, 10 | HasId, 11 | Id, 12 | }; 13 | 14 | pub(crate) struct SubGraph { 15 | entry: Id, 16 | nodes: Vec, 17 | } 18 | 19 | impl SubGraph { 20 | pub(crate) fn entry(&self) -> Id { 21 | self.entry 22 | } 23 | 24 | pub(crate) fn nodes(&self) -> &[Id] { 25 | &self.nodes 26 | } 27 | } 28 | 29 | struct GraphWalker { 30 | nodes: Vec, 31 | cursor: usize, 32 | } 33 | 34 | impl GraphWalker { 35 | fn new() -> Self { 36 | Self { 37 | nodes: Vec::new(), 38 | cursor: 0, 39 | } 40 | } 41 | 42 | fn push(&mut self, node: Id) { 43 | if !self.nodes.contains(&node) { 44 | self.nodes.push(node); 45 | } 46 | } 47 | 48 | fn next(&mut self) -> Option { 49 | if self.cursor < self.nodes.len() { 50 | let elem = self.nodes[self.cursor]; 51 | self.cursor += 1; 52 | Some(elem) 53 | } else { 54 | None 55 | } 56 | } 57 | } 58 | 59 | struct SubCFGBuilder<'a> { 60 | cfg: &'a CFG, 61 | entry_points: HashSet, 62 | subgraphs: Vec>, 63 | } 64 | 65 | impl<'a> SubCFGBuilder<'a> { 66 | fn new(cfg: &'a CFG) -> Self { 67 | let mut entry_points = HashSet::new(); 68 | entry_points.insert(cfg.entry()); 69 | 70 | Self { 71 | cfg, 72 | entry_points, 73 | subgraphs: Vec::new(), 74 | } 75 | } 76 | 77 | fn find_entry_points(&mut self) { 78 | for bb in self.cfg.iter_basic_blocks() { 79 | match bb.ops().last() { 80 | Some(Op::FireEvent { 81 | .. 82 | }) => { 83 | if !bb.edges().is_empty() { 84 | assert_eq!(bb.edges().len(), 1); 85 | let Edge::Next(target) = bb.edges()[0] else { unreachable!() }; 86 | self.entry_points.insert(target); 87 | } 88 | }, 89 | Some(Op::Jump { 90 | .. 91 | }) => { 92 | for edge in bb.edges() { 93 | if let Edge::Next(target) = edge { 94 | self.entry_points.insert(*target); 95 | } 96 | } 97 | }, 98 | _ => {}, 99 | } 100 | } 101 | } 102 | 103 | fn calculate_subgraphs(&mut self) { 104 | self.subgraphs.clear(); 105 | let mut walker = GraphWalker::new(); 106 | 107 | for entry in &self.entry_points { 108 | let mut subgraph = Vec::new(); 109 | walker.push(*entry); 110 | 111 | while let Some(next) = walker.next() { 112 | subgraph.push(next); 113 | 114 | let bb = self.cfg.basic_block(next).unwrap(); 115 | for edge in bb.edges() { 116 | let target = edge.target(); 117 | 118 | if !self.entry_points.contains(&target) { 119 | walker.push(target); 120 | } 121 | } 122 | } 123 | 124 | self.subgraphs.push(subgraph); 125 | } 126 | } 127 | 128 | fn check_connectedness(&mut self) -> bool { 129 | let prev_len = self.entry_points.len(); 130 | 131 | for subgraph in &self.subgraphs { 132 | for id in subgraph { 133 | let bb = self.cfg.basic_block(*id).unwrap(); 134 | 135 | for edge in bb.edges() { 136 | let target = edge.target(); 137 | 138 | if !subgraph.contains(&target) { 139 | self.entry_points.insert(target); 140 | } 141 | } 142 | } 143 | } 144 | 145 | self.entry_points.len() > prev_len 146 | } 147 | 148 | fn verify(&self) { 149 | let mut num_bb = 0; 150 | 151 | for subgraph in &self.subgraphs { 152 | assert!(!subgraph.is_empty()); 153 | num_bb += subgraph.len(); 154 | } 155 | 156 | assert_eq!(self.cfg.num_basic_blocks(), num_bb); 157 | } 158 | 159 | fn into_subgraphs(self) -> Vec { 160 | let mut ret = Vec::new(); 161 | 162 | for subgraph in self.subgraphs { 163 | let entry = subgraph[0]; 164 | ret.push(SubGraph { 165 | entry, 166 | nodes: subgraph, 167 | }); 168 | } 169 | 170 | ret 171 | } 172 | } 173 | 174 | pub(crate) fn split_into_subgraphs(func: &Function) -> Vec { 175 | if func.perfect() { 176 | let mut builder = SubCFGBuilder::new(func.cfg()); 177 | builder.find_entry_points(); 178 | builder.calculate_subgraphs(); 179 | 180 | while builder.check_connectedness() { 181 | builder.calculate_subgraphs(); 182 | } 183 | 184 | builder.verify(); 185 | builder.into_subgraphs() 186 | } else { 187 | let mut ret = Vec::new(); 188 | 189 | for bb in func.cfg().iter_basic_blocks() { 190 | ret.push(SubGraph { 191 | entry: bb.id(), 192 | nodes: vec![bb.id()], 193 | }); 194 | } 195 | 196 | ret 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /squid/src/backends/clang/concretize.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::frontend::{ 4 | ao::Op, 5 | ChunkContent, 6 | FixedVec, 7 | Pointer, 8 | ProcessImage, 9 | VAddr, 10 | }; 11 | 12 | fn lookup_pointer(image: &ProcessImage, pointer: &Pointer, table: &mut HashMap) { 13 | if table.contains_key(pointer) { 14 | return; 15 | } 16 | 17 | let addr = match pointer { 18 | Pointer::Function(pointer) => { 19 | let chunk = image 20 | .elf(pointer.elf) 21 | .unwrap() 22 | .section(pointer.section) 23 | .unwrap() 24 | .symbol(pointer.symbol) 25 | .unwrap() 26 | .chunk(pointer.chunk) 27 | .unwrap(); 28 | 29 | let ChunkContent::Code(func) = chunk.content() else { unreachable!() }; 30 | let entry = func.cfg().entry(); 31 | 32 | func.cfg().basic_block(entry).unwrap().vaddr().unwrap() 33 | }, 34 | Pointer::BasicBlock(pointer) => { 35 | let chunk = image 36 | .elf(pointer.elf) 37 | .unwrap() 38 | .section(pointer.section) 39 | .unwrap() 40 | .symbol(pointer.symbol) 41 | .unwrap() 42 | .chunk(pointer.chunk) 43 | .unwrap(); 44 | 45 | let ChunkContent::Code(func) = chunk.content() else { unreachable!() }; 46 | 47 | func.cfg().basic_block(pointer.bb).unwrap().vaddr().unwrap() 48 | }, 49 | Pointer::Global(pointer) => { 50 | let addr = image 51 | .elf(pointer.elf) 52 | .unwrap() 53 | .section(pointer.section) 54 | .unwrap() 55 | .symbol(pointer.symbol) 56 | .unwrap() 57 | .chunk(pointer.chunk) 58 | .unwrap() 59 | .vaddr(); 60 | addr + pointer.offset as VAddr 61 | }, 62 | Pointer::Null => 0, 63 | }; 64 | 65 | table.insert(pointer.clone(), addr); 66 | } 67 | 68 | pub(crate) fn concretize(image: &mut ProcessImage) { 69 | let mut table = HashMap::::new(); 70 | 71 | /* Build lookup table for all pointers */ 72 | for elf in image.iter_elfs() { 73 | for section in elf.iter_sections() { 74 | for symbol in section.iter_symbols() { 75 | for chunk in symbol.iter_chunks() { 76 | match chunk.content() { 77 | ChunkContent::Pointer(pointer) => { 78 | lookup_pointer(image, pointer, &mut table); 79 | }, 80 | ChunkContent::Code(func) => { 81 | for bb in func.cfg().iter_basic_blocks() { 82 | for op in bb.ops() { 83 | if let Op::LoadPointer { 84 | pointer, 85 | .. 86 | } = op 87 | { 88 | lookup_pointer(image, pointer, &mut table); 89 | } 90 | } 91 | } 92 | }, 93 | ChunkContent::Data { 94 | .. 95 | } => {}, 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | /* Resolve every pointer */ 103 | for elf in image.iter_elfs_mut() { 104 | for section in elf.iter_sections_mut() { 105 | let perms = section.perms(); 106 | 107 | for symbol in section.iter_symbols_mut() { 108 | for chunk in symbol.iter_chunks_mut() { 109 | match chunk.content_mut() { 110 | ChunkContent::Pointer(pointer) => { 111 | let addr = *table.get(pointer).unwrap(); 112 | let bytes = FixedVec::lock(addr.to_le_bytes()); 113 | let perms = FixedVec::lock(vec![perms; bytes.len()]); 114 | chunk.set_content(ChunkContent::Data { 115 | bytes, 116 | perms, 117 | }); 118 | }, 119 | ChunkContent::Code(func) => { 120 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 121 | bb.set_cursor(0); 122 | 123 | while let Some(op) = bb.cursor_op() { 124 | if let Op::LoadPointer { 125 | dst, 126 | pointer, 127 | } = op 128 | { 129 | let vaddr = *table.get(pointer).unwrap(); 130 | bb.replace_op(Op::LoadVirtAddr { 131 | dst: *dst, 132 | vaddr, 133 | }); 134 | } 135 | 136 | if !bb.move_cursor_forward() { 137 | break; 138 | } 139 | } 140 | } 141 | }, 142 | ChunkContent::Data { 143 | .. 144 | } => {}, 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /squid/src/backends/clang/event.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap as HashMap; 2 | 3 | use crate::{ 4 | frontend::{ 5 | ao::Op, 6 | ChunkContent, 7 | ProcessImage, 8 | }, 9 | runtime::SnapshotId, 10 | }; 11 | 12 | pub(crate) struct EventChannel { 13 | /// `[length] [data...]` 14 | data: Vec, 15 | snapshots: HashMap>, 16 | capacity: usize, 17 | } 18 | 19 | impl EventChannel { 20 | pub(crate) fn new(image: &ProcessImage) -> Self { 21 | let mut size = 0; 22 | 23 | for elf in image.iter_elfs() { 24 | for section in elf.iter_sections() { 25 | for symbol in section.iter_symbols() { 26 | for chunk in symbol.iter_chunks() { 27 | if let ChunkContent::Code(func) = chunk.content() { 28 | for bb in func.cfg().iter_basic_blocks() { 29 | for op in bb.ops() { 30 | match op { 31 | Op::PushEventArgs { 32 | args, 33 | } => { 34 | size = std::cmp::max(size, args.len()); 35 | }, 36 | Op::CollectEventReturns { 37 | vars, 38 | } => { 39 | size = std::cmp::max(size, vars.len()); 40 | }, 41 | _ => {}, 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | Self { 52 | data: vec![0; 1 + size], 53 | snapshots: HashMap::default(), 54 | capacity: size, 55 | } 56 | } 57 | 58 | pub(crate) fn get(&self, size: usize) -> Option<&[u64]> { 59 | self.data.get(1..1 + size) 60 | } 61 | 62 | pub(crate) fn get_mut(&mut self, size: usize) -> Option<&mut [u64]> { 63 | self.data.get_mut(1..1 + size) 64 | } 65 | 66 | pub(crate) fn length(&self) -> usize { 67 | *(unsafe { self.data.get_unchecked(0) }) as usize 68 | } 69 | 70 | pub(crate) fn set_length(&mut self, length: usize) { 71 | *(unsafe { self.data.get_unchecked_mut(0) }) = length as u64; 72 | } 73 | 74 | pub(crate) fn raw_pointer(&mut self) -> *mut u64 { 75 | self.data.as_mut_ptr() 76 | } 77 | 78 | pub(crate) fn take_snapshot(&mut self, id: SnapshotId) { 79 | self.snapshots.insert(id, self.data.clone()); 80 | } 81 | 82 | pub(crate) fn restore_snapshot_unchecked(&mut self, id: SnapshotId) { 83 | let snapshot = self.snapshots.get(&id); 84 | let snapshot = unsafe { snapshot.unwrap_unchecked() }; 85 | self.data.copy_from_slice(snapshot); 86 | } 87 | 88 | pub(crate) fn delete_snapshot_unchecked(&mut self, id: SnapshotId) { 89 | self.snapshots.remove(&id); 90 | } 91 | 92 | pub(crate) fn capacity(&self) -> usize { 93 | self.capacity 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /squid/src/backends/clang/exec.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::Deref, 3 | path::Path, 4 | }; 5 | 6 | use libloading::{ 7 | Library, 8 | Symbol, 9 | }; 10 | 11 | use crate::{ 12 | backends::clang::{ 13 | EventChannel, 14 | Memory, 15 | Registers, 16 | }, 17 | frontend::VAddr, 18 | }; 19 | 20 | /// The raw return code of the "AOT", i.e. the AOT-compiled C code. 21 | #[repr(u32)] 22 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 23 | pub enum AOTReturnCode { 24 | /// The guest threw an event 25 | Event = 0, 26 | /// An invalid state of the codegen has been reached. This means that there is an error with the codegen. 27 | InvalidState = 1, 28 | /// The guest tried to jump to a virtual address that does not exist or does not contain code. 29 | InvalidJumpTarget = 2, 30 | /// The guest tried to read from a virtual address that does not point to readable memory. 31 | InvalidRead = 3, 32 | /// The guest tried to read from a virtual address that contains uninitialized data. 33 | UninitializedRead = 4, 34 | /// The guest has terminated and execution cannot continue. 35 | End = 5, 36 | /// The guest tried to write to a virtual address that does not point to writable memory. 37 | InvalidWrite = 6, 38 | /// The event channel does not the exact number of values the guest expected 39 | InvalidEventChannel = 7, 40 | /// The guest attempted a division by zero 41 | DivByZero = 8, 42 | /// The guest exceed the maximum number of allowed RISC-V instructions 43 | Timeout = 9, 44 | /// The guest attempted an addition that overflowed 45 | IntegerOverflow = 10, 46 | } 47 | 48 | #[repr(C)] 49 | #[derive(Default)] 50 | pub(crate) struct AOTReturnBuffer { 51 | /// Corresponds to AOTReturnCode 52 | pub(crate) code: u32, 53 | pub(crate) arg0: usize, 54 | pub(crate) arg1: usize, 55 | pub(crate) count: usize, 56 | } 57 | 58 | type AOTEntrypoint = extern "C" fn(usize, usize, usize, usize, usize) -> usize; 59 | 60 | pub(crate) struct AOTExecutor { 61 | entrypoint: AOTEntrypoint, 62 | return_buf: AOTReturnBuffer, 63 | } 64 | 65 | impl AOTExecutor { 66 | pub(crate) fn new(binary_path: &Path) -> Self { 67 | let entrypoint = unsafe { 68 | let lib = Library::new(binary_path).unwrap(); 69 | let f: Symbol = lib.get(b"run").unwrap(); 70 | let f = *f.deref(); 71 | std::mem::forget(lib); 72 | f 73 | }; 74 | 75 | Self { 76 | entrypoint, 77 | return_buf: AOTReturnBuffer::default(), 78 | } 79 | } 80 | 81 | #[inline] 82 | pub(crate) fn run( 83 | &mut self, 84 | memory: &mut Memory, 85 | event_channel: &mut EventChannel, 86 | registers: &mut Registers, 87 | var_storage: &mut Vec, 88 | ) -> VAddr { 89 | let next_pc = (self.entrypoint)( 90 | memory.raw_pointer() as usize, 91 | event_channel.raw_pointer() as usize, 92 | registers.raw_pointer() as usize, 93 | &mut self.return_buf as *mut _ as usize, 94 | var_storage.as_mut_ptr() as usize, 95 | ); 96 | next_pc as VAddr 97 | } 98 | 99 | #[inline] 100 | pub(crate) fn return_code(&self) -> AOTReturnCode { 101 | debug_assert!(self.return_buf.code <= 10); 102 | unsafe { std::mem::transmute::(self.return_buf.code) } 103 | } 104 | 105 | #[inline] 106 | pub(crate) fn return_arg0(&self) -> usize { 107 | self.return_buf.arg0 108 | } 109 | 110 | #[inline] 111 | pub(crate) fn return_arg1(&self) -> usize { 112 | self.return_buf.arg1 113 | } 114 | 115 | #[inline] 116 | pub(crate) fn executed_instructions(&self) -> usize { 117 | self.return_buf.count 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /squid/src/backends/clang/mod.rs: -------------------------------------------------------------------------------- 1 | //! The `clang` backend provided by `squid`. 2 | //! 3 | //! This backend generates C code from the functions in the process 4 | //! image, compiles the C code with clang as a shared object file and 5 | //! loads that via dlopen() into the address space. 6 | //! We take little detour over clang to get the best possible LLVM codegen 7 | //! and thus the best performance. 8 | //! 9 | //! Our original idea was to just emit the LLVM IR ourselves but no LLVM frontend is 10 | //! as good as clang anyways and C is a lot easier to generate than the LLVM IR, so this solution was 11 | //! less work and yielded better results. 12 | 13 | mod address; 14 | #[allow(clippy::module_inception)] 15 | mod backend; 16 | mod codegen; 17 | mod concretize; 18 | mod event; 19 | mod exec; 20 | mod heap; 21 | mod memory; 22 | mod preprocess; 23 | mod registers; 24 | mod runtime; 25 | mod symbol; 26 | mod variables; 27 | 28 | pub(crate) use address::{ 29 | get_entrypoint_address, 30 | AddressLayouter, 31 | AddressSpace, 32 | }; 33 | pub(crate) use codegen::CLifter; 34 | pub use codegen::CLifterError; 35 | pub(crate) use concretize::concretize; 36 | pub(crate) use event::EventChannel; 37 | pub(crate) use exec::AOTExecutor; 38 | pub(crate) use heap::Heap; 39 | pub(crate) use memory::{ 40 | populate_stack, 41 | Memory, 42 | PAGE_SIZE, 43 | }; 44 | pub(crate) use preprocess::{ 45 | insert_entrypoint, 46 | insert_guard_pages, 47 | }; 48 | pub(crate) use registers::Registers; 49 | pub(crate) use variables::VariableStorage; 50 | pub mod perms; 51 | pub use backend::{ 52 | ClangBackend, 53 | ClangBackendBuilder, 54 | ClangBackendError, 55 | }; 56 | pub use exec::AOTReturnCode; 57 | pub use heap::{ 58 | HeapChunk, 59 | HeapError, 60 | }; 61 | pub use runtime::{ 62 | ClangRuntime, 63 | ClangRuntimeFault, 64 | }; 65 | pub use symbol::{ 66 | Symbol, 67 | SymbolType, 68 | SymbolVisibility, 69 | }; 70 | -------------------------------------------------------------------------------- /squid/src/backends/clang/perms.rs: -------------------------------------------------------------------------------- 1 | //! The permission bits used by the ClangBackend 2 | #![allow(missing_docs)] 3 | 4 | use crate::frontend::Perms; 5 | 6 | pub const PERM_NONE: u8 = 0; 7 | /// Indicates that a byte has not been written to. Gets cleared on write. 8 | pub const PERM_UNINIT: u8 = 8; 9 | pub const PERM_READ: u8 = 4; 10 | pub const PERM_WRITE: u8 = 2; 11 | pub const PERM_EXEC: u8 = 1; 12 | 13 | /// Used internally by the dynstore_* methods. Signals the start of a heap chunk. DO NOT USE. 14 | pub const PERM_CHUNK_START: u8 = 16; 15 | 16 | /// Used internally by the dynstore_* methods. Signals the end of a heap chunk. DO NOT USE. 17 | pub const PERM_CHUNK_END: u8 = 32; 18 | 19 | pub(crate) fn convert_loader_perms(perms: Perms) -> u8 { 20 | let mut new_perms = 0; 21 | 22 | if perms.is_readable() { 23 | new_perms |= PERM_READ; 24 | } 25 | 26 | if perms.is_writable() { 27 | new_perms |= PERM_WRITE; 28 | } 29 | 30 | if perms.is_executable() { 31 | new_perms |= PERM_EXEC; 32 | } 33 | 34 | new_perms 35 | } 36 | -------------------------------------------------------------------------------- /squid/src/backends/clang/preprocess.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backends::clang::PAGE_SIZE, 3 | event::EventPool, 4 | frontend::{ 5 | ao::{ 6 | events::EVENT_BREAKPOINT, 7 | BasicBlock, 8 | Edge, 9 | CFG, 10 | }, 11 | BasicBlockPointer, 12 | Chunk, 13 | ChunkContent, 14 | Elf, 15 | FunctionPointer, 16 | Perms, 17 | Pointer, 18 | ProcessImage, 19 | Section, 20 | Symbol, 21 | }, 22 | riscv::register::GpRegister, 23 | }; 24 | 25 | pub(crate) fn insert_guard_pages(image: &mut ProcessImage) { 26 | /* Left guard page */ 27 | let chunk = Chunk::builder().vaddr(0).uninitialized_data(PAGE_SIZE, Perms::default()).build().unwrap(); 28 | 29 | let mut symbol = Symbol::builder().vaddr(0).size(PAGE_SIZE).build().unwrap(); 30 | symbol.insert_chunk(chunk); 31 | 32 | let mut section = Section::builder().perms(Perms::default()).vaddr(0).size(PAGE_SIZE).build().unwrap(); 33 | section.insert_symbol(symbol); 34 | 35 | let mut elf = Elf::builder().path("").build().unwrap(); 36 | elf.insert_section(section); 37 | 38 | image.set_cursor(0); 39 | image.insert_elf(elf); 40 | 41 | /* Right guard page */ 42 | let chunk = Chunk::builder().vaddr(0).uninitialized_data(PAGE_SIZE, Perms::default()).build().unwrap(); 43 | 44 | let mut symbol = Symbol::builder().vaddr(0).size(PAGE_SIZE).build().unwrap(); 45 | symbol.insert_chunk(chunk); 46 | 47 | let mut section = Section::builder().perms(Perms::default()).vaddr(0).size(PAGE_SIZE).build().unwrap(); 48 | section.insert_symbol(symbol); 49 | 50 | let mut elf = Elf::builder().path("").build().unwrap(); 51 | elf.insert_section(section); 52 | 53 | image.move_cursor_beyond_end(); 54 | image.insert_elf(elf); 55 | } 56 | 57 | pub(crate) fn insert_entrypoint(image: &mut ProcessImage, event_pool: &EventPool) { 58 | let mut cfg = CFG::new(); 59 | let mut functions: Vec = image.constructors().iter().rev().cloned().collect(); 60 | functions.insert(0, image.entrypoint().clone()); 61 | 62 | /* Start with the last basic block in function */ 63 | let mut bb = BasicBlock::new(); 64 | let event = event_pool.get_event(EVENT_BREAKPOINT).unwrap(); 65 | bb.fire_event(event); 66 | let mut next_id = cfg.add_basic_block(bb); 67 | cfg.basic_block_mut(next_id).unwrap().add_edge(Edge::Next(next_id)); 68 | cfg.set_entry(next_id); // temporary 69 | 70 | /* Inser the code into the process image */ 71 | let chunk = Chunk::builder().vaddr(0).size(1).code(cfg).unwrap().build().unwrap(); 72 | 73 | let mut symbol = Symbol::builder().vaddr(0).size(1).private_name("").build().unwrap(); 74 | let chunk = symbol.insert_chunk(chunk); 75 | 76 | let mut perms = Perms::default(); 77 | perms.make_executable(); 78 | let mut section = Section::builder().perms(perms).vaddr(0).size(1).build().unwrap(); 79 | let symbol = section.insert_symbol(symbol); 80 | 81 | let mut elf = Elf::builder().path("").build().unwrap(); 82 | let section = elf.insert_section(section); 83 | 84 | let elf = image.insert_elf(elf); 85 | 86 | /* Continue building the function */ 87 | let ChunkContent::Code(func) = image 88 | .elf_mut(elf) 89 | .unwrap() 90 | .section_mut(section) 91 | .unwrap() 92 | .symbol_mut(symbol) 93 | .unwrap() 94 | .chunk_mut(chunk) 95 | .unwrap() 96 | .content_mut() 97 | else { 98 | unreachable!() 99 | }; 100 | 101 | for function in functions { 102 | let mut bb = BasicBlock::new(); 103 | let ra = bb.load_pointer(Pointer::BasicBlock(BasicBlockPointer { 104 | elf, 105 | section, 106 | symbol, 107 | chunk, 108 | bb: next_id, 109 | })); 110 | bb.store_gp_register(GpRegister::ra, ra).unwrap(); 111 | let pointer = bb.load_pointer(Pointer::Function(function.clone())); 112 | bb.jump(pointer).unwrap(); 113 | bb.add_edge(Edge::Next(next_id)); 114 | 115 | next_id = func.cfg_mut().add_basic_block(bb); 116 | } 117 | 118 | func.cfg_mut().set_entry(next_id); 119 | image.set_entrypoint(FunctionPointer { 120 | elf, 121 | section, 122 | symbol, 123 | chunk, 124 | }); 125 | } 126 | -------------------------------------------------------------------------------- /squid/src/backends/clang/registers.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap as HashMap; 2 | 3 | use crate::{ 4 | frontend::VAddr, 5 | riscv::register::CsrRegister, 6 | runtime::SnapshotId, 7 | }; 8 | 9 | /// [32x GP] [pc] [instr] [32x FP] [fcsr] 10 | const REGISTER_COUNT: usize = 32 + 1 + 1 + 32 + 1; 11 | 12 | pub(crate) const INSTR_INDEX: usize = 33; 13 | pub(crate) const PC_INDEX: usize = 32; 14 | 15 | pub(crate) struct Registers { 16 | content: [u64; REGISTER_COUNT], 17 | snapshots: HashMap, 18 | } 19 | 20 | impl Registers { 21 | pub(crate) fn new() -> Self { 22 | Self { 23 | content: [0; REGISTER_COUNT], 24 | snapshots: HashMap::default(), 25 | } 26 | } 27 | 28 | pub(crate) fn take_snapshot(&mut self, id: SnapshotId) { 29 | self.snapshots.insert(id, self.content); 30 | } 31 | 32 | pub(crate) fn restore_snapshot_unchecked(&mut self, id: SnapshotId) { 33 | let snapshot = self.snapshots.get(&id); 34 | let snapshot = unsafe { snapshot.unwrap_unchecked() }; 35 | 36 | self.content.copy_from_slice(snapshot); 37 | } 38 | 39 | pub(crate) fn delete_snapshot_unchecked(&mut self, id: SnapshotId) { 40 | self.snapshots.remove(&id); 41 | } 42 | 43 | pub(crate) fn get_gp(&self, reg: usize) -> u64 { 44 | *unsafe { self.content.get_unchecked(reg) } 45 | } 46 | 47 | pub(crate) fn set_gp(&mut self, reg: usize, value: u64) { 48 | *unsafe { self.content.get_unchecked_mut(reg) } = value; 49 | } 50 | 51 | pub(crate) fn get_fp(&self, reg: usize) -> u64 { 52 | *unsafe { self.content.get_unchecked(34 + reg) } 53 | } 54 | 55 | pub(crate) fn set_fp(&mut self, reg: usize, value: u64) { 56 | *unsafe { self.content.get_unchecked_mut(34 + reg) } = value; 57 | } 58 | 59 | pub(crate) fn get_csr(&self, csr: CsrRegister) -> u64 { 60 | let value = *unsafe { self.content.get_unchecked(66) }; 61 | match csr { 62 | CsrRegister::fcsr => value & 0xFF, 63 | CsrRegister::fflags => value & 0b11111, 64 | CsrRegister::frm => (value >> 5) & 0b111, 65 | } 66 | } 67 | 68 | pub(crate) fn set_csr(&mut self, csr: CsrRegister, value: u64) { 69 | let old_value = *unsafe { self.content.get_unchecked(66) }; 70 | let new_value = match csr { 71 | CsrRegister::fcsr => value & 0xFF, 72 | CsrRegister::fflags => (old_value & 0b11100000) | (value & 0b00011111), 73 | CsrRegister::frm => (value & 0b11100000) | (old_value & 0b00011111), 74 | }; 75 | *unsafe { self.content.get_unchecked_mut(64) } = new_value; 76 | } 77 | 78 | pub(crate) fn get_pc(&self) -> VAddr { 79 | *unsafe { self.content.get_unchecked(PC_INDEX) } as VAddr 80 | } 81 | 82 | pub(crate) fn set_pc(&mut self, pc: VAddr) { 83 | *unsafe { self.content.get_unchecked_mut(PC_INDEX) } = pc; 84 | } 85 | 86 | pub(crate) fn raw_pointer(&mut self) -> *mut u64 { 87 | self.content.as_mut_ptr() 88 | } 89 | 90 | pub(crate) fn get_last_instr(&self) -> VAddr { 91 | *unsafe { self.content.get_unchecked(INSTR_INDEX) } as VAddr 92 | } 93 | 94 | pub(crate) fn set_last_instr(&mut self, instr: VAddr) { 95 | *unsafe { self.content.get_unchecked_mut(INSTR_INDEX) } = instr; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /squid/src/backends/clang/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{ 4 | backends::clang::{ 5 | address::POINTER_CODE_SHIFT, 6 | AddressSpace, 7 | }, 8 | frontend::{ 9 | ProcessImage, 10 | VAddr, 11 | }, 12 | }; 13 | 14 | /// The type of a symbol in the runtime's symbol store 15 | #[allow(missing_docs)] 16 | #[derive(Debug, Clone)] 17 | pub enum SymbolType { 18 | Function, 19 | Data, 20 | } 21 | 22 | /// The visibility of a symbol in the runtime's symbol store 23 | #[allow(missing_docs)] 24 | #[derive(Debug, Clone)] 25 | pub enum SymbolVisibility { 26 | Public, 27 | Private, 28 | } 29 | 30 | /// The Symbols in the ClangRuntime are constructed from the symbols in the 31 | /// process image except that there is one runtime Symbol for every private + 32 | /// public name of a process image symbol. 33 | #[derive(Debug, Clone)] 34 | pub struct Symbol { 35 | name: String, 36 | visibility: SymbolVisibility, 37 | address: VAddr, 38 | size: usize, 39 | typ: SymbolType, 40 | } 41 | 42 | impl Symbol { 43 | /// Return true if this symbol is a function 44 | pub fn is_function(&self) -> bool { 45 | matches!(self.typ, SymbolType::Function) 46 | } 47 | 48 | /// Return true if this symbol holds data 49 | pub fn is_data(&self) -> bool { 50 | matches!(self.typ, SymbolType::Data) 51 | } 52 | 53 | /// Get the virtual address of this symbol 54 | pub fn address(&self) -> VAddr { 55 | self.address 56 | } 57 | 58 | /// Get the size of this symbol 59 | pub fn size(&self) -> usize { 60 | self.size 61 | } 62 | 63 | /// Return true if this is a publicly exported symbol (from the .dynsym) 64 | pub fn is_public(&self) -> bool { 65 | matches!(self.visibility, SymbolVisibility::Public) 66 | } 67 | 68 | /// Return true if this is a private symbol (from .symtab) 69 | pub fn is_private(&self) -> bool { 70 | matches!(self.visibility, SymbolVisibility::Private) 71 | } 72 | 73 | /// Get the name of this symbol 74 | pub fn name(&self) -> &str { 75 | &self.name 76 | } 77 | 78 | /// Check whether this symbol contains the given address 79 | pub fn contains_address(&self, addr: VAddr) -> bool { 80 | match AddressSpace::decode(addr) { 81 | AddressSpace::Data(_) => self.address <= addr && addr < self.address + self.size as VAddr, 82 | AddressSpace::Code(_) => { 83 | let search_addr = addr >> POINTER_CODE_SHIFT; 84 | let this_addr = self.address >> POINTER_CODE_SHIFT; 85 | this_addr <= search_addr && search_addr < this_addr + self.size as VAddr 86 | }, 87 | } 88 | } 89 | } 90 | 91 | pub(crate) fn create_symbol_store(image: &ProcessImage) -> HashMap> { 92 | let mut ret = HashMap::new(); 93 | 94 | for elf in image.iter_elfs() { 95 | let file: &str = &elf.path().file_name().unwrap().to_string_lossy(); 96 | 97 | for section in elf.iter_sections() { 98 | for symbol in section.iter_symbols() { 99 | let end_addr = symbol.last_addr() + 1; 100 | 101 | for public_name in symbol.public_names() { 102 | let address = symbol.public_name(public_name).unwrap(); 103 | let size = (end_addr - address) as usize; 104 | let typ = if section.perms().is_executable() { SymbolType::Function } else { SymbolType::Data }; 105 | 106 | ret.entry(file.to_string()).or_insert_with(Vec::new).push(Symbol { 107 | name: public_name.clone(), 108 | visibility: SymbolVisibility::Public, 109 | address, 110 | size, 111 | typ, 112 | }); 113 | } 114 | 115 | for private_name in symbol.private_names() { 116 | let address = symbol.private_name(private_name).unwrap(); 117 | let size = (end_addr - address) as usize; 118 | let typ = if section.perms().is_executable() { SymbolType::Function } else { SymbolType::Data }; 119 | 120 | ret.entry(file.to_string()).or_insert_with(Vec::new).push(Symbol { 121 | name: private_name.clone(), 122 | visibility: SymbolVisibility::Private, 123 | address, 124 | size, 125 | typ, 126 | }); 127 | } 128 | } 129 | } 130 | } 131 | 132 | ret 133 | } 134 | -------------------------------------------------------------------------------- /squid/src/backends/clang/variables.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::frontend::{ 4 | ao::{ 5 | Edge, 6 | Function, 7 | CFG, 8 | }, 9 | ChunkContent, 10 | HasId, 11 | Id, 12 | ProcessImage, 13 | VAddr, 14 | }; 15 | 16 | fn find_definition(preds: &[(Id, Id)], defs: &HashMap>, var: usize, mut curr: Id, cfg: &CFG) -> VAddr { 17 | loop { 18 | let mut found = false; 19 | 20 | for (to, from) in preds { 21 | if *to == curr { 22 | curr = *from; 23 | found = true; 24 | break; 25 | } 26 | } 27 | 28 | if !found { 29 | unreachable!("Variable import has no matching export"); 30 | } 31 | 32 | let flags = defs.get(&curr).unwrap(); 33 | 34 | if flags[var] { 35 | return cfg.basic_block(curr).unwrap().vaddr().unwrap(); 36 | } 37 | } 38 | } 39 | 40 | pub(crate) struct VariableStorage { 41 | ids: HashMap<(VAddr, usize), usize>, 42 | counter: usize, 43 | num_variables: usize, 44 | } 45 | 46 | impl VariableStorage { 47 | pub(crate) fn new(image: &ProcessImage) -> Self { 48 | let mut storage = Self { 49 | ids: HashMap::default(), 50 | counter: 0, 51 | num_variables: 0, 52 | }; 53 | 54 | for elf in image.iter_elfs() { 55 | for section in elf.iter_sections() { 56 | for symbol in section.iter_symbols() { 57 | for chunk in symbol.iter_chunks() { 58 | if let ChunkContent::Code(func) = chunk.content() { 59 | storage.check_function(func); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | storage 67 | } 68 | 69 | pub(crate) fn num_variables(&self) -> usize { 70 | self.num_variables 71 | } 72 | 73 | pub(crate) fn get_static_id(&self, bb: VAddr, var: usize) -> Option { 74 | self.ids.get(&(bb, var)).copied() 75 | } 76 | 77 | fn new_static_id(&mut self, def_bb: VAddr, var: usize) -> usize { 78 | if let Some(id) = self.get_static_id(def_bb, var) { 79 | return id; 80 | } 81 | 82 | let ret = self.counter; 83 | self.counter += 1; 84 | ret 85 | } 86 | 87 | fn check_function(&mut self, func: &Function) { 88 | self.counter = 0; 89 | 90 | /* First calculate predecessors */ 91 | let mut preds = Vec::new(); 92 | 93 | for bb in func.cfg().iter_basic_blocks() { 94 | for edge in bb.edges() { 95 | if let Edge::Next(target) = edge { 96 | assert_ne!(*target, func.cfg().entry()); 97 | preds.push((*target, bb.id())); 98 | } 99 | } 100 | } 101 | 102 | /* Then find variable definitions */ 103 | let mut defs = HashMap::new(); 104 | 105 | for bb in func.cfg().iter_basic_blocks() { 106 | let mut flags = vec![false; bb.num_variables()]; 107 | 108 | for op in bb.ops() { 109 | for out_var in op.output_variables() { 110 | flags[out_var.id()] = true; 111 | } 112 | } 113 | 114 | defs.insert(bb.id(), flags); 115 | } 116 | 117 | /* Finally, check for variable imports */ 118 | for bb in func.cfg().iter_basic_blocks() { 119 | let flags = defs.get(&bb.id()).unwrap(); 120 | 121 | for op in bb.ops() { 122 | for in_var in op.input_variables() { 123 | if !flags[in_var.id()] { 124 | let def_bb = find_definition(&preds, &defs, in_var.id(), bb.id(), func.cfg()); 125 | let static_id = self.new_static_id(def_bb, in_var.id()); 126 | self.ids.insert((def_bb, in_var.id()), static_id); 127 | self.ids.insert((bb.vaddr().unwrap(), in_var.id()), static_id); 128 | } 129 | } 130 | } 131 | } 132 | 133 | self.num_variables = std::cmp::max(self.counter, self.num_variables); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /squid/src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains all backends that are provided by `squid` 2 | 3 | mod backend; 4 | pub mod clang; 5 | pub use backend::Backend; 6 | -------------------------------------------------------------------------------- /squid/src/compiler.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::{ 6 | backends::Backend, 7 | event::EventPool, 8 | frontend::{ 9 | ao::events::{ 10 | EVENT_BREAKPOINT, 11 | EVENT_SYSCALL, 12 | }, 13 | LoaderError, 14 | ProcessImage, 15 | ProcessImageBuilder, 16 | }, 17 | logger::Logger, 18 | passes::{ 19 | Pass, 20 | VerifyerPass, 21 | VerifyerPassError, 22 | }, 23 | }; 24 | 25 | #[derive(Error, Debug)] 26 | pub enum CompilerError<'a> { 27 | #[error("Loader has not been configured correctly: {0}")] 28 | LoaderOptionNotSet(&'static str), 29 | 30 | #[error("The frontend had an error: {0}")] 31 | LoaderError(#[from] LoaderError), 32 | 33 | #[error("The backend had an error: {0}")] 34 | BackendError(Box), 35 | 36 | #[error("Verification failed: {0}")] 37 | VerificationError(#[from] VerifyerPassError), 38 | } 39 | 40 | /// The `Loader` is a helper struct that creates a [`Compiler`] by 41 | /// - Loading an ELF file 42 | /// - collecting its shared dependencies 43 | /// - lifting all RISC-V code into an IR 44 | /// - making code and data available in a [`ProcessImage`] 45 | pub struct Loader { 46 | binary: Option, 47 | library_paths: Vec, 48 | preloads: Vec, 49 | ignore_missing_deps: bool, 50 | } 51 | 52 | impl Loader { 53 | pub(crate) fn new() -> Self { 54 | Self { 55 | binary: None, 56 | library_paths: Vec::new(), 57 | preloads: Vec::new(), 58 | ignore_missing_deps: false, 59 | } 60 | } 61 | 62 | /// Set the target binary that is going to be emulated 63 | pub fn binary>(mut self, binary: P) -> Self { 64 | self.binary = Some(binary.into()); 65 | self 66 | } 67 | 68 | /// Add the given directory to the search paths of the ELF loader. 69 | /// The shared dependencies of the binary will be searched here (similar to LD_LIBRARY_PATH). 70 | /// You can specify this option multiple times. 71 | pub fn search_path>(mut self, search_path: P) -> Self { 72 | self.library_paths.push(search_path.into()); 73 | self 74 | } 75 | 76 | /// Add multiple directories to the search paths of the ELF loader. 77 | /// Does the same as [`Loader::search_path`]. 78 | pub fn search_paths(mut self, search_paths: I) -> Self 79 | where 80 | I: IntoIterator, 81 | P: Into, 82 | { 83 | for search_path in search_paths { 84 | self.library_paths.push(search_path.into()); 85 | } 86 | self 87 | } 88 | 89 | /// Preload this library (similar to LD_PRELOAD). 90 | /// You can specify this option multiple times. 91 | pub fn preload>(mut self, library: P) -> Self { 92 | self.preloads.push(library.into()); 93 | self 94 | } 95 | 96 | /// Preload multiple libraries. 97 | /// Does the same as [`Loader::preload`]. 98 | pub fn preloads(mut self, preloads: I) -> Self 99 | where 100 | I: IntoIterator, 101 | P: Into, 102 | { 103 | for preload in preloads { 104 | self.preloads.push(preload.into()); 105 | } 106 | self 107 | } 108 | 109 | /// If `flag` is set to `true`, the ELF loader will not throw an error when it cannot 110 | /// find a dependency in the provided search paths. 111 | pub fn ignore_missing_dependencies(mut self, flag: bool) -> Self { 112 | self.ignore_missing_deps = flag; 113 | self 114 | } 115 | 116 | /// Create a [`Compiler`] by loading the target binary 117 | pub fn load(self) -> Result> { 118 | let binary = self.binary.ok_or(CompilerError::LoaderOptionNotSet("binary has not been set"))?; 119 | 120 | let mut logger = Logger::spinner(); 121 | logger.set_title("Building process image"); 122 | 123 | let mut event_pool = EventPool::new(); 124 | event_pool.add_event(EVENT_SYSCALL); 125 | event_pool.add_event(EVENT_BREAKPOINT); 126 | 127 | let image = ProcessImageBuilder::build(binary, &self.library_paths, &self.preloads, self.ignore_missing_deps, &mut event_pool, &logger)?; 128 | let mut compiler = Compiler { 129 | image, 130 | event_pool, 131 | modified: false, 132 | }; 133 | drop(logger); 134 | 135 | compiler.verify()?; 136 | Ok(compiler) 137 | } 138 | } 139 | 140 | /// The Compiler is the center piece of `squid`. It loads ELF files, runs passes and launches a backend 141 | /// to obtain a [Runtime](crate::runtime::Runtime). 142 | /// 143 | /// To obtain a `Compiler` instance, call [`Compiler::loader`]. Then you can run one or more passes 144 | /// with [`Compiler::run_pass`] before compiling the process image with [`Compiler::compile`]. 145 | #[derive(Debug)] 146 | pub struct Compiler { 147 | pub(crate) image: ProcessImage, 148 | pub(crate) event_pool: EventPool, 149 | modified: bool, 150 | } 151 | 152 | impl Compiler { 153 | /// Create a new `Compiler` by symbolically loading an ELF file and creating a process image. 154 | /// See the [`Loader`] for details about how the ELF-loader can be configured. 155 | pub fn loader() -> Loader { 156 | Loader::new() 157 | } 158 | 159 | /// Run a pass to inspect or modify the process image. 160 | /// 161 | /// # Arguments 162 | /// 1. `pass`: Anything that implements the [`Pass`] trait 163 | pub fn run_pass

(&mut self, pass: &mut P) -> Result<(), P::Error> 164 | where 165 | P: Pass, 166 | { 167 | self.modified = true; 168 | 169 | let mut logger = Logger::spinner(); 170 | logger.set_title(format!("Running Pass: {}", pass.name())); 171 | logger.set_prefix(pass.name()); 172 | 173 | let ret = pass.run(&mut self.image, &mut self.event_pool, &logger); 174 | 175 | logger.clear_prefix(); 176 | ret 177 | } 178 | 179 | fn verify(&mut self) -> Result<(), VerifyerPassError> { 180 | let mut verifyer = VerifyerPass::new(false); 181 | self.run_pass(&mut verifyer)?; 182 | self.modified = false; 183 | Ok(()) 184 | } 185 | 186 | /// Compile the process image and create a [Runtime](crate::runtime::Runtime). 187 | /// The type of the runtime is determined by the backend. Each backend can have its own runtime. 188 | /// 189 | /// # Arguments 190 | /// 1. `backend`: Anything that implements the [`Backend`] trait 191 | pub fn compile<'a, B: Backend>(mut self, mut backend: B) -> Result> 192 | where 193 | ::Error: 'a, 194 | { 195 | if self.modified { 196 | self.verify()?; 197 | } 198 | 199 | let mut logger = Logger::spinner(); 200 | logger.set_title(format!("Compiling with backend: {}", backend.name())); 201 | logger.set_prefix(backend.name()); 202 | 203 | let ret = match backend.create_runtime(self.image, self.event_pool, &logger) { 204 | Ok(runtime) => runtime, 205 | Err(err) => return Err(CompilerError::BackendError(Box::new(err))), 206 | }; 207 | 208 | logger.clear_prefix(); 209 | logger.info("Compilation successful"); 210 | Ok(ret) 211 | } 212 | 213 | /// Access the process image, which is the result of symbolically loading a binary 214 | pub fn process_image(&self) -> &ProcessImage { 215 | &self.image 216 | } 217 | 218 | /// Access the event pool, which manages all events that can be thrown at runtime 219 | pub fn event_pool(&self) -> &EventPool { 220 | &self.event_pool 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /squid/src/event.rs: -------------------------------------------------------------------------------- 1 | //! Contains the [`EventPool`] and other helper structs. 2 | 3 | use std::collections::HashMap; 4 | 5 | /// The ID of the syscall event is always the same: this value 6 | pub const EVENT_SYSCALL: usize = 0; 7 | 8 | /// The ID of the breakpint event is always the same: this value 9 | pub const EVENT_BREAKPOINT: usize = 1; 10 | 11 | /// The ID of an event in the [`EventPool`] 12 | #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] 13 | pub struct EventId(usize); 14 | 15 | impl EventId { 16 | fn new(id: usize) -> Self { 17 | EventId(id) 18 | } 19 | 20 | /// Get the ID as a usize 21 | pub fn id(&self) -> usize { 22 | self.0 23 | } 24 | } 25 | 26 | /// The EventPool manages all events that might be thrown during emulation of the target program. 27 | #[derive(Debug)] 28 | pub struct EventPool { 29 | events: HashMap, 30 | cursor: usize, 31 | } 32 | 33 | impl EventPool { 34 | pub(crate) fn new() -> Self { 35 | Self { 36 | events: HashMap::new(), 37 | cursor: 0, 38 | } 39 | } 40 | 41 | fn next_event(&mut self) -> EventId { 42 | let cursor = self.cursor; 43 | self.cursor = cursor.checked_add(1).expect("Ran out of possible event ids"); 44 | EventId::new(cursor) 45 | } 46 | 47 | /// Given an event name, create a new event ID and return it. 48 | /// If the event name already exists, the corresponding ID is returned. 49 | pub fn add_event + AsRef>(&mut self, name: S) -> EventId { 50 | if let Some(id) = self.get_event(name.as_ref()) { 51 | id 52 | } else { 53 | let id = self.next_event(); 54 | self.events.insert(name.into(), id); 55 | id 56 | } 57 | } 58 | 59 | /// Given an event name, return the corresponding event ID 60 | pub fn get_event>(&self, name: S) -> Option { 61 | self.events.get(name.as_ref()).copied() 62 | } 63 | 64 | /// Delete an event from the event pool 65 | pub fn remove_event>(&mut self, name: S) -> Option { 66 | self.events.remove(name.as_ref()) 67 | } 68 | 69 | /// Return the total number of events 70 | pub fn len(&self) -> usize { 71 | self.events.len() 72 | } 73 | 74 | /// Return whether any events have been created 75 | pub fn is_empty(&self) -> bool { 76 | self.events.is_empty() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /squid/src/frontend/ao/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::frontend::{ 4 | ao::cfg::CFGError, 5 | image::VAddr, 6 | }; 7 | 8 | /// This error type shows everything that can go wrong when lifting RISC-V code 9 | /// into the ΑΩ IR. 10 | #[allow(missing_docs)] 11 | #[derive(Error, Debug)] 12 | pub enum AoError { 13 | #[error("Invalid operation size: {0}")] 14 | InvalidOpSize(usize), 15 | 16 | #[error("Invalid jump target address: {0:#x}")] 17 | InvalidJumpTarget(VAddr), 18 | 19 | #[error("No basic block at address {0:#x}")] 20 | BasicBlockNotFound(VAddr), 21 | 22 | #[error("{0:?}")] 23 | CFGError(#[from] CFGError), 24 | 25 | #[error("CFG is disonnected and no ewe metadata was available")] 26 | CFGDisconnected, 27 | 28 | #[error("Unknown RISC-V instruction: {0:x}")] 29 | UnknownInstr(u32), 30 | 31 | #[error("Invalid rounding mode: {0}")] 32 | InvalidRm(u64), 33 | 34 | #[error("Invalid variable type: {0}")] 35 | InvalidVarType(String), 36 | } 37 | -------------------------------------------------------------------------------- /squid/src/frontend/ao/events.rs: -------------------------------------------------------------------------------- 1 | //! Contains all the events that can be thrown by RISC-V instructions 2 | 3 | /// The syscall event is thrown by the ECALL instruction 4 | pub const EVENT_SYSCALL: &str = "builtin::syscall"; 5 | 6 | /// The breakpoint event is thrown by the EBREAK instruction 7 | pub const EVENT_BREAKPOINT: &str = "builtin::breakpoint"; 8 | -------------------------------------------------------------------------------- /squid/src/frontend/ao/func.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::ao::CFG; 2 | 3 | /// A Function corresponds to a function in an ELF file except that the 4 | /// CFG has been reconstructed and the code has been lifted into the ΑΩ IR. 5 | #[derive(Hash)] 6 | pub struct Function { 7 | cfg: CFG, 8 | perfect: bool, 9 | } 10 | 11 | impl Function { 12 | pub(crate) fn new(cfg: CFG, perfect: bool) -> Self { 13 | Self { 14 | cfg, 15 | perfect, 16 | } 17 | } 18 | 19 | /// Get the CFG of this function 20 | pub fn cfg(&self) -> &CFG { 21 | &self.cfg 22 | } 23 | 24 | /// Get the CFG of this function 25 | pub fn cfg_mut(&mut self) -> &mut CFG { 26 | &mut self.cfg 27 | } 28 | 29 | /// The CFG of this function is considered "perfect" if the graph is not disconnected 30 | pub fn perfect(&self) -> bool { 31 | self.perfect 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /squid/src/frontend/ao/mod.rs: -------------------------------------------------------------------------------- 1 | //! ΑΩ (ao) stands for "atomic operations" and is the IR of `squid` 2 | 3 | mod cfg; 4 | mod error; 5 | mod func; 6 | mod lifter; 7 | mod ops; 8 | 9 | pub(crate) use lifter::Lifter; 10 | 11 | pub mod engine; 12 | pub mod events; 13 | pub use cfg::{ 14 | BasicBlock, 15 | CFGError, 16 | Edge, 17 | CFG, 18 | }; 19 | pub use error::AoError; 20 | pub use func::Function; 21 | pub use ops::{ 22 | ArithmeticBehavior, 23 | Comparison, 24 | Half, 25 | Op, 26 | Register, 27 | Signedness, 28 | Var, 29 | VarType, 30 | }; 31 | -------------------------------------------------------------------------------- /squid/src/frontend/dependency.rs: -------------------------------------------------------------------------------- 1 | use std::path::{ 2 | Path, 3 | PathBuf, 4 | }; 5 | 6 | struct Dependency { 7 | path: PathBuf, 8 | edges: Vec, 9 | } 10 | 11 | impl Dependency { 12 | fn new>(path: P) -> Self { 13 | Self { 14 | path: path.into(), 15 | edges: Vec::new(), 16 | } 17 | } 18 | } 19 | 20 | pub struct DependencyGraph { 21 | nodes: Vec, 22 | cursor: usize, 23 | } 24 | 25 | impl DependencyGraph { 26 | pub fn new() -> Self { 27 | Self { 28 | nodes: Vec::new(), 29 | cursor: 0, 30 | } 31 | } 32 | 33 | pub fn add_node>(&mut self, path: P) -> usize { 34 | let mut i = 0; 35 | 36 | while i < self.nodes.len() { 37 | if self.nodes[i].path.as_path() == path.as_ref() { 38 | return i; 39 | } 40 | 41 | i += 1; 42 | } 43 | 44 | self.nodes.push(Dependency::new(path.as_ref())); 45 | i 46 | } 47 | 48 | pub fn add_edge(&mut self, from: usize, to: usize) { 49 | if !self.nodes[from].edges.contains(&to) { 50 | self.nodes[from].edges.push(to); 51 | } 52 | } 53 | 54 | pub fn next_unvisited(&mut self) -> Option<(usize, &Path)> { 55 | if self.cursor < self.nodes.len() { 56 | let ret = Some((self.cursor, self.nodes[self.cursor].path.as_path())); 57 | self.cursor += 1; 58 | ret 59 | } else { 60 | None 61 | } 62 | } 63 | 64 | pub fn walk(&self, start: usize) -> Vec { 65 | let mut ret = Vec::::new(); 66 | let mut cursor = 0; 67 | 68 | ret.push(start); 69 | 70 | while cursor < ret.len() { 71 | for edge in &self.nodes[ret[cursor]].edges { 72 | if !ret.contains(edge) { 73 | ret.push(*edge); 74 | } 75 | } 76 | 77 | cursor += 1; 78 | } 79 | 80 | ret 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /squid/src/frontend/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::{ 4 | frontend::ao::AoError, 5 | passes::VerifyerPassError, 6 | }; 7 | 8 | /// This enum contains all error cases that can occur during creation of the process image 9 | #[allow(missing_docs)] 10 | #[derive(Error, Debug)] 11 | pub enum LoaderError { 12 | #[error("The dependency '{0}' cannot be found in the search paths")] 13 | DependencyNotFound(String), 14 | 15 | #[error("Invalid ELF binary: {0}")] 16 | InvalidELF(String), 17 | 18 | #[error("Symbol resolution error: {0}")] 19 | SymbolResolutionError(String), 20 | 21 | #[error("Error with ewe file: {0}")] 22 | EweError(String), 23 | 24 | #[error("{0:?}")] 25 | AoError(#[from] AoError), 26 | 27 | #[error("Error symbolizing code: {0}")] 28 | CodeSymbolizationError(String), 29 | 30 | #[error("Symbolic loading of ELF file failed: {0}")] 31 | LoadingError(String), 32 | 33 | #[error("IO error: {0}")] 34 | IOError(String), 35 | 36 | #[error("The symbolic ELF loader produced an invalid process image: {0}")] 37 | InvalidProcessImage(String), 38 | 39 | #[error("Verification failed: {0}")] 40 | VerificationError(#[from] VerifyerPassError), 41 | } 42 | -------------------------------------------------------------------------------- /squid/src/frontend/fixedvec.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{ 3 | Debug, 4 | Formatter, 5 | }, 6 | ops::{ 7 | Index, 8 | IndexMut, 9 | }, 10 | slice::SliceIndex, 11 | }; 12 | 13 | /// A FixedVec is a vector that cannot change its length, only its content 14 | #[derive(Hash)] 15 | pub struct FixedVec { 16 | inner: Vec, 17 | } 18 | 19 | impl FixedVec { 20 | /// Fixate a vector and return a FixedVec 21 | pub fn lock>>(inner: V) -> Self { 22 | Self { 23 | inner: inner.into(), 24 | } 25 | } 26 | 27 | /// Unfixate this vector 28 | pub fn unlock(self) -> Vec { 29 | self.inner 30 | } 31 | 32 | /// The length of this vector 33 | pub fn len(&self) -> usize { 34 | self.inner.len() 35 | } 36 | 37 | /// Check whether this vector is empty 38 | pub fn is_empty(&self) -> bool { 39 | self.inner.is_empty() 40 | } 41 | } 42 | 43 | impl Index for FixedVec 44 | where 45 | I: SliceIndex<[T]>, 46 | { 47 | type Output = I::Output; 48 | 49 | fn index(&self, index: I) -> &Self::Output { 50 | self.inner.index(index) 51 | } 52 | } 53 | 54 | impl IndexMut for FixedVec 55 | where 56 | I: SliceIndex<[T]>, 57 | { 58 | fn index_mut(&mut self, index: I) -> &mut Self::Output { 59 | self.inner.index_mut(index) 60 | } 61 | } 62 | 63 | impl Debug for FixedVec { 64 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 65 | write!(f, "FixedVec") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /squid/src/frontend/idmap.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | hash::{ 3 | Hash, 4 | Hasher, 5 | }, 6 | slice::{ 7 | Iter, 8 | IterMut, 9 | }, 10 | }; 11 | 12 | /// The ID of an element in the ProcessImage 13 | pub type Id = usize; 14 | 15 | pub(crate) trait HasIdMut { 16 | fn id_mut(&mut self) -> &mut Id; 17 | } 18 | 19 | /// This trait provides access to the ID of elements in the process image 20 | pub trait HasId { 21 | /// Retrieve the ID of this element 22 | fn id(&self) -> Id; 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub(crate) struct IdFactory { 27 | cursor: Id, 28 | } 29 | 30 | impl IdFactory { 31 | pub(crate) fn new() -> Self { 32 | Self { 33 | cursor: Id::default().wrapping_add(1), 34 | } 35 | } 36 | 37 | pub(crate) fn next(&mut self) -> Id { 38 | let ret = self.cursor; 39 | self.cursor = ret.checked_add(1).expect("Ran out of possible ID values"); 40 | ret 41 | } 42 | 43 | pub(crate) fn reset(&mut self) { 44 | self.cursor = Id::default().wrapping_add(1); 45 | } 46 | } 47 | 48 | pub(crate) type IdMapValues<'a, T> = Iter<'a, T>; 49 | pub(crate) type IdMapValuesMut<'a, T> = IterMut<'a, T>; 50 | 51 | #[derive(Clone, Debug)] 52 | pub(crate) struct IdMap 53 | where 54 | T: HasId + HasIdMut + Hash, 55 | { 56 | data: Vec, 57 | factory: IdFactory, 58 | } 59 | 60 | impl Hash for IdMap 61 | where 62 | T: HasId + HasIdMut + Hash, 63 | { 64 | fn hash(&self, state: &mut H) { 65 | state.write_usize(self.data.len()); 66 | for elem in &self.data { 67 | elem.hash(state); 68 | } 69 | } 70 | } 71 | 72 | impl IdMap 73 | where 74 | T: HasId + HasIdMut + Hash, 75 | { 76 | pub(crate) fn new() -> Self { 77 | Self { 78 | data: Vec::new(), 79 | factory: IdFactory::new(), 80 | } 81 | } 82 | 83 | pub(crate) fn clear(&mut self) { 84 | self.data.clear(); 85 | self.factory.reset(); 86 | } 87 | 88 | pub(crate) fn reserve_id(&mut self, elem: &mut T) -> Id { 89 | let mut id = elem.id(); 90 | 91 | if id == Id::default() { 92 | id = self.factory.next(); 93 | *elem.id_mut() = id; 94 | } 95 | 96 | id 97 | } 98 | 99 | pub(crate) fn insert(&mut self, mut elem: T) -> Id { 100 | let id = self.reserve_id(&mut elem); 101 | self.data.push(elem); 102 | id 103 | } 104 | 105 | pub(crate) fn get(&self, id: Id) -> Option<&T> { 106 | self.data.iter().find(|&elem| elem.id() == id) 107 | } 108 | 109 | pub(crate) fn get_mut(&mut self, id: Id) -> Option<&mut T> { 110 | self.data.iter_mut().find(|elem| elem.id() == id) 111 | } 112 | 113 | pub(crate) fn values(&self) -> IdMapValues { 114 | self.data.iter() 115 | } 116 | 117 | pub(crate) fn values_mut(&mut self) -> IdMapValuesMut { 118 | self.data.iter_mut() 119 | } 120 | 121 | pub(crate) fn get_at(&self, idx: usize) -> Option<&T> { 122 | self.data.get(idx) 123 | } 124 | 125 | pub(crate) fn get_at_mut(&mut self, idx: usize) -> Option<&mut T> { 126 | self.data.get_mut(idx) 127 | } 128 | 129 | pub(crate) fn insert_at(&mut self, idx: usize, mut elem: T) -> Id { 130 | let id = self.reserve_id(&mut elem); 131 | self.data.insert(idx, elem); 132 | id 133 | } 134 | 135 | pub(crate) fn remove_at(&mut self, idx: usize) -> T { 136 | self.data.remove(idx) 137 | } 138 | 139 | pub(crate) fn len(&self) -> usize { 140 | self.data.len() 141 | } 142 | 143 | pub(crate) fn is_empty(&self) -> bool { 144 | self.data.is_empty() 145 | } 146 | } 147 | 148 | macro_rules! idmap_functions { 149 | ($parent:ty, $child:ty, $suffix:ident) => { 150 | /// Accessor methods that handle the children of this process image element 151 | impl $parent { 152 | /// Get the cursor of this process image element 153 | pub fn cursor(&self) -> usize { 154 | self.cursor 155 | } 156 | 157 | /// Set the cursor of this process image element 158 | pub fn set_cursor(&mut self, cursor: usize) { 159 | self.cursor = cursor; 160 | } 161 | 162 | /// Move the cursor of this process image element to the end of its children list. 163 | /// This enables appending children. 164 | pub fn move_cursor_beyond_end(&mut self) { 165 | self.cursor = self.idmap.len(); 166 | } 167 | 168 | /// Increment the cursor of this process image element or return `false` if the cursor 169 | /// is already at the last child 170 | pub fn move_cursor_forward(&mut self) -> bool { 171 | if self.cursor >= self.idmap.len().saturating_sub(1) { 172 | false 173 | } else { 174 | self.cursor += 1; 175 | true 176 | } 177 | } 178 | 179 | /// Decrement the cursor of this process image element or return `false` if it's already 180 | /// at the first child 181 | pub fn move_cursor_backwards(&mut self) -> bool { 182 | if self.cursor == 0 { 183 | false 184 | } else { 185 | self.cursor -= 1; 186 | true 187 | } 188 | } 189 | 190 | /// Retrieve the child with the given ID of this process image element 191 | pub fn $suffix(&self, id: Id) -> Option<&$child> { 192 | self.idmap.get(id) 193 | } 194 | 195 | paste! { 196 | /// Get the number of children for this process image element 197 | pub fn [](&self) -> usize { 198 | self.idmap.len() 199 | } 200 | 201 | /// Get the child at the current cursor position for this process image element 202 | pub fn [](&self) -> Option<&$child> { 203 | self.idmap.get_at(self.cursor) 204 | } 205 | 206 | /// Get the child at the current cursor position for this process image element 207 | pub fn [](&mut self) -> Option<&mut $child> { 208 | self.idmap.get_at_mut(self.cursor) 209 | } 210 | 211 | /// Insert the given child into the list of children at the current cursor position for this process image element 212 | pub fn [](&mut self, child: $child) -> Id { 213 | self.idmap.insert_at(self.cursor, child) 214 | } 215 | 216 | /// Delete the child at the current cursor position of this process image element and return it 217 | pub fn [](&mut self) -> $child { 218 | self.idmap.remove_at(self.cursor) 219 | } 220 | 221 | /// Iterate over all children of this process image element independent of the current cursor position 222 | pub fn [](&self) -> IdMapValues<$child> { 223 | self.idmap.values() 224 | } 225 | 226 | /// Iterate over all children of this process image element independent of the current cursor position 227 | pub fn [](&mut self) -> IdMapValuesMut<$child> { 228 | self.idmap.values_mut() 229 | } 230 | 231 | /// Retrieve the child with the given ID of this process image element 232 | pub fn [<$suffix _mut>](&mut self, id: Id) -> Option<&mut $child> { 233 | self.idmap.get_mut(id) 234 | } 235 | } 236 | } 237 | }; 238 | } 239 | pub(crate) use idmap_functions; 240 | -------------------------------------------------------------------------------- /squid/src/frontend/mod.rs: -------------------------------------------------------------------------------- 1 | //! The frontend handles everything related to symbolic ELF-loading and the creation 2 | //! of the process image 3 | 4 | mod chunk; 5 | mod dependency; 6 | mod elf; 7 | mod error; 8 | mod fixedvec; 9 | mod idmap; 10 | mod image; 11 | mod perms; 12 | mod pointer; 13 | mod reloc; 14 | mod section; 15 | mod symbol; 16 | 17 | pub(crate) mod symbolization_passes; 18 | 19 | pub(crate) use image::ProcessImageBuilder; 20 | pub(crate) use reloc::Relocation; 21 | 22 | pub mod ao; 23 | pub use chunk::{ 24 | Chunk, 25 | ChunkBuilder, 26 | ChunkContent, 27 | }; 28 | pub use elf::{ 29 | Elf, 30 | ElfBuilder, 31 | }; 32 | pub use error::LoaderError; 33 | pub use fixedvec::FixedVec; 34 | pub use idmap::{ 35 | HasId, 36 | Id, 37 | }; 38 | pub use image::{ 39 | ProcessImage, 40 | VAddr, 41 | }; 42 | pub use perms::Perms; 43 | pub use pointer::{ 44 | BasicBlockPointer, 45 | FunctionPointer, 46 | GlobalPointer, 47 | Pointer, 48 | }; 49 | pub use section::{ 50 | Section, 51 | SectionBuilder, 52 | }; 53 | pub use symbol::{ 54 | Symbol, 55 | SymbolBuilder, 56 | }; 57 | -------------------------------------------------------------------------------- /squid/src/frontend/perms.rs: -------------------------------------------------------------------------------- 1 | use goblin; 2 | 3 | const PERM_X: u8 = 1; 4 | const PERM_W: u8 = 2; 5 | const PERM_R: u8 = 4; 6 | 7 | /// The permissions memory and elements in the process image can have 8 | #[derive(Copy, Clone, PartialEq, Debug, Default, Hash)] 9 | pub struct Perms(u8); 10 | 11 | impl Perms { 12 | pub(crate) fn from_segment_flags(flags: u32) -> Self { 13 | let mut perms = Perms(0); 14 | 15 | if (flags & goblin::elf::program_header::PF_X) != 0 { 16 | perms.make_executable(); 17 | } 18 | 19 | if (flags & goblin::elf::program_header::PF_W) != 0 { 20 | perms.make_writable(); 21 | } 22 | 23 | if (flags & goblin::elf::program_header::PF_R) != 0 { 24 | perms.make_readable(); 25 | } 26 | 27 | perms 28 | } 29 | 30 | pub(crate) fn from_section_header(header: &goblin::elf::section_header::SectionHeader) -> Self { 31 | let mut perms = Perms(0); 32 | perms.make_readable(); 33 | 34 | if header.is_executable() { 35 | perms.make_executable(); 36 | } 37 | 38 | if header.is_writable() { 39 | perms.make_writable(); 40 | } 41 | 42 | perms 43 | } 44 | 45 | #[allow(missing_docs)] 46 | pub fn is_executable(&self) -> bool { 47 | (self.0 & PERM_X) != 0 48 | } 49 | 50 | #[allow(missing_docs)] 51 | pub fn is_writable(&self) -> bool { 52 | (self.0 & PERM_W) != 0 53 | } 54 | 55 | #[allow(missing_docs)] 56 | pub fn is_readable(&self) -> bool { 57 | (self.0 & PERM_R) != 0 58 | } 59 | 60 | #[allow(missing_docs)] 61 | pub fn is_inaccessible(&self) -> bool { 62 | self.0 == 0 63 | } 64 | 65 | #[allow(missing_docs)] 66 | pub fn clear_executable(&mut self) { 67 | self.0 &= !PERM_X; 68 | } 69 | 70 | #[allow(missing_docs)] 71 | pub fn clear_writable(&mut self) { 72 | self.0 &= !PERM_W; 73 | } 74 | 75 | #[allow(missing_docs)] 76 | pub fn clear_readable(&mut self) { 77 | self.0 &= !PERM_R; 78 | } 79 | 80 | #[allow(missing_docs)] 81 | pub fn make_executable(&mut self) { 82 | self.0 |= PERM_X 83 | } 84 | 85 | #[allow(missing_docs)] 86 | pub fn make_readable(&mut self) { 87 | self.0 |= PERM_R 88 | } 89 | 90 | #[allow(missing_docs)] 91 | pub fn make_writable(&mut self) { 92 | self.0 |= PERM_W 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /squid/src/frontend/pointer.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::idmap::Id; 2 | 3 | /// A symbolic pointer to a global variable in the process image 4 | #[allow(missing_docs)] 5 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 6 | pub struct GlobalPointer { 7 | pub elf: Id, 8 | pub section: Id, 9 | pub symbol: Id, 10 | pub chunk: Id, 11 | pub offset: usize, 12 | } 13 | 14 | /// A symbolic pointer to a basic block in a function of the process image 15 | #[allow(missing_docs)] 16 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 17 | pub struct BasicBlockPointer { 18 | pub elf: Id, 19 | pub section: Id, 20 | pub symbol: Id, 21 | pub chunk: Id, 22 | pub bb: Id, 23 | } 24 | 25 | /// A symbolic pointer to a function in the process image. 26 | /// Note that the difference to the [`BasicBlockPointer`] is that this pointer 27 | /// always points to the entrypoint bb of a function, even when the entrypoint changes. 28 | #[allow(missing_docs)] 29 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 30 | pub struct FunctionPointer { 31 | pub elf: Id, 32 | pub section: Id, 33 | pub symbol: Id, 34 | pub chunk: Id, 35 | } 36 | 37 | /// A symbolic pointer that points to a leaf in the process image 38 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 39 | pub enum Pointer { 40 | /// This pointer points nowhere 41 | Null, 42 | 43 | /// This pointer points to a global variable 44 | Global(GlobalPointer), 45 | 46 | /// This pointer points to a specific basic block of a function 47 | BasicBlock(BasicBlockPointer), 48 | 49 | /// This pointer points to a specific function 50 | Function(FunctionPointer), 51 | } 52 | -------------------------------------------------------------------------------- /squid/src/frontend/reloc.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::error::LoaderError; 2 | 3 | #[derive(Debug, PartialEq, Eq, Hash)] 4 | pub(crate) enum Relocation { 5 | Offset(usize), 6 | SymbolImport(String), 7 | TlsSymbolImport(String), 8 | } 9 | 10 | fn reloc_size(typ: u32) -> usize { 11 | match typ { 12 | goblin::elf::reloc::R_RISCV_RELATIVE 13 | | goblin::elf::reloc::R_RISCV_TLS_TPREL64 14 | | goblin::elf::reloc::R_RISCV_JUMP_SLOT 15 | | goblin::elf::reloc::R_RISCV_64 => 8, 16 | _ => todo!("{}", typ), 17 | } 18 | } 19 | 20 | pub(crate) fn parse_relocations( 21 | elf: &goblin::elf::Elf, 22 | parent_start: u64, 23 | parent_size: u64, 24 | ) -> Result, LoaderError> { 25 | if !elf.dynrels.is_empty() { 26 | return Err(LoaderError::InvalidELF("Binary uses rels instead of relas".to_string())); 27 | } 28 | 29 | let mut result = Vec::new(); 30 | 31 | for rela in elf.dynrelas.iter() { 32 | let vaddr = rela.r_offset; 33 | let size = reloc_size(rela.r_type); 34 | 35 | let has_start = parent_start <= vaddr && vaddr < parent_start + parent_size; 36 | let has_end = 37 | parent_start <= (vaddr + size as u64 - 1) && (vaddr + size as u64 - 1) < parent_start + parent_size; 38 | 39 | if has_start != has_end { 40 | return Err(LoaderError::InvalidELF(format!( 41 | "Relocation at {:#x} overlaps parent {:#x}", 42 | vaddr, parent_start 43 | ))); 44 | } 45 | 46 | if has_start { 47 | let addend = rela.r_addend.unwrap(); 48 | 49 | let rel = if rela.r_sym == 0 { 50 | // Use zero as symbol value 51 | 52 | match rela.r_type { 53 | goblin::elf::reloc::R_RISCV_RELATIVE => Relocation::Offset(addend as usize), 54 | t => todo!("{}", t), 55 | } 56 | } else { 57 | let linked_sym = elf.dynsyms.get(rela.r_sym).unwrap(); 58 | 59 | // Is this even correct? Nobody documents shit anymore 60 | if linked_sym.is_import() { 61 | assert_eq!(addend, 0); 62 | 63 | let name = elf.dynstrtab.get_at(linked_sym.st_name).unwrap().to_string(); 64 | 65 | match rela.r_type { 66 | goblin::elf::reloc::R_RISCV_JUMP_SLOT => Relocation::SymbolImport(name), 67 | goblin::elf::reloc::R_RISCV_64 => Relocation::SymbolImport(name), 68 | goblin::elf::reloc::R_RISCV_TLS_TPREL64 => Relocation::TlsSymbolImport(name), 69 | t => todo!("{}", t), 70 | } 71 | } else { 72 | match rela.r_type { 73 | goblin::elf::reloc::R_RISCV_64 => { 74 | Relocation::Offset(linked_sym.st_value as usize + addend as usize) 75 | }, 76 | goblin::elf::reloc::R_RISCV_JUMP_SLOT => Relocation::Offset(linked_sym.st_value as usize), 77 | t => todo!("{}", t), 78 | } 79 | } 80 | }; 81 | 82 | result.push((rel, vaddr, size)); 83 | } 84 | } 85 | 86 | Ok(result) 87 | } 88 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/arith.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::ao::{ 2 | engine::{ 3 | Engine, 4 | Value, 5 | }, 6 | Function, 7 | Op, 8 | }; 9 | 10 | pub(crate) struct EliminateArithmeticPass {} 11 | 12 | impl EliminateArithmeticPass { 13 | #[allow(clippy::new_without_default)] 14 | pub(crate) fn new() -> Self { 15 | Self {} 16 | } 17 | 18 | pub(crate) fn run(&mut self, func: &mut Function) -> Result<(), String> { 19 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 20 | let vars = { 21 | let mut engine = Engine::<()>::attach(bb, None); 22 | 23 | if let Err(err) = engine.execute() { 24 | return Err(format!("EngineError: {}", err)); 25 | } 26 | 27 | engine.vars().to_owned() 28 | }; 29 | 30 | bb.set_cursor(0); 31 | 32 | while let Some(op) = bb.cursor_op() { 33 | #[allow(clippy::single_match)] 34 | match op { 35 | Op::Add { 36 | dst, 37 | src1, 38 | src2, 39 | .. 40 | } => { 41 | if let Value::Integer(0) = &vars[src1.id()] { 42 | bb.replace_op(Op::Copy { 43 | dst: *dst, 44 | src: *src2, 45 | }); 46 | } else if let Value::Integer(0) = &vars[src2.id()] { 47 | bb.replace_op(Op::Copy { 48 | dst: *dst, 49 | src: *src1, 50 | }); 51 | } 52 | }, 53 | //TODO: maybe support more arithmetic operators 54 | _ => {}, 55 | } 56 | 57 | if !bb.move_cursor_forward() { 58 | break; 59 | } 60 | } 61 | } 62 | 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/deadcode.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::ao::Function; 2 | 3 | pub(crate) struct DeadCodeEliminationPass {} 4 | 5 | impl DeadCodeEliminationPass { 6 | #[allow(clippy::new_without_default)] 7 | pub(crate) fn new() -> Self { 8 | Self {} 9 | } 10 | 11 | pub(crate) fn run(&mut self, func: &mut Function) -> Result<(), String> { 12 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 13 | let mut used = vec![0; bb.num_variables()]; 14 | 15 | for op in bb.ops() { 16 | for var in op.input_variables() { 17 | used[var.id()] += 1; 18 | } 19 | } 20 | 21 | bb.move_cursor_beyond_end(); 22 | bb.move_cursor_backwards(); 23 | 24 | while let Some(op) = bb.cursor_op() { 25 | let mut output_used = false; 26 | let mut has_output = false; 27 | 28 | for var in op.output_variables() { 29 | output_used |= used[var.id()] > 0; 30 | has_output = true; 31 | } 32 | 33 | if has_output && !output_used { 34 | let op = bb.delete_op(); 35 | 36 | for var in op.input_variables() { 37 | used[var.id()] -= 1; 38 | } 39 | } 40 | 41 | if !bb.move_cursor_backwards() { 42 | break; 43 | } 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/empty.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::{ 2 | ao::{ 3 | Edge, 4 | Function, 5 | }, 6 | HasId, 7 | }; 8 | 9 | pub(crate) struct EliminateEmptyBasicBlocksPass {} 10 | 11 | impl EliminateEmptyBasicBlocksPass { 12 | #[allow(clippy::new_without_default)] 13 | pub(crate) fn new() -> Self { 14 | Self {} 15 | } 16 | 17 | pub(crate) fn run(&mut self, func: &mut Function) -> Result<(), String> { 18 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 19 | if bb.ops().is_empty() { 20 | let mut next = None; 21 | 22 | for edge in bb.edges() { 23 | match edge { 24 | Edge::Next(id) => { 25 | assert!(next.is_none()); 26 | next = Some(*id); 27 | }, 28 | Edge::Jump(_) => return Err("Empty basic block with a jump edge".to_string()), 29 | } 30 | } 31 | 32 | if let Some(next) = next { 33 | assert_ne!(bb.id(), next); 34 | 35 | bb.set_cursor(0); 36 | bb.nop(); 37 | } else { 38 | todo!("???"); 39 | } 40 | } 41 | } 42 | 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::ao::{ 2 | Function, 3 | Op, 4 | }; 5 | 6 | pub(crate) struct MetadataPass {} 7 | 8 | impl MetadataPass { 9 | pub(crate) fn new() -> Self { 10 | Self {} 11 | } 12 | 13 | pub(crate) fn run(&self, func: &mut Function) -> Result<(), String> { 14 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 15 | let mut count = 0; 16 | let mut prev_cursor = None; 17 | 18 | bb.set_cursor(0); 19 | 20 | while let Some(op) = bb.cursor_op() { 21 | if let Op::NextInstruction { 22 | .. 23 | } = op 24 | { 25 | if count == 0 { 26 | if let Some(prev_cursor) = prev_cursor { 27 | let current_cursor = bb.cursor(); 28 | bb.set_cursor(prev_cursor); 29 | bb.delete_op(); 30 | bb.set_cursor(current_cursor - 1); 31 | } 32 | } 33 | 34 | count = 0; 35 | prev_cursor = Some(bb.cursor()); 36 | } else { 37 | count += 1; 38 | } 39 | 40 | if !bb.move_cursor_forward() { 41 | break; 42 | } 43 | } 44 | 45 | if count == 0 && prev_cursor.is_some() { 46 | bb.delete_op(); 47 | } 48 | } 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/mod.rs: -------------------------------------------------------------------------------- 1 | mod arith; 2 | mod deadcode; 3 | mod empty; 4 | mod metadata; 5 | mod propagate; 6 | mod regcache; 7 | mod relax; 8 | mod symbolize; 9 | 10 | pub(crate) use arith::EliminateArithmeticPass; 11 | pub(crate) use deadcode::DeadCodeEliminationPass; 12 | pub(crate) use empty::EliminateEmptyBasicBlocksPass; 13 | pub(crate) use metadata::MetadataPass; 14 | pub(crate) use propagate::AddressPropagationPass; 15 | pub(crate) use regcache::RegisterCachingPass; 16 | pub(crate) use relax::HandleRelaxationPass; 17 | pub(crate) use symbolize::SymbolizerPass; 18 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/regcache.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::ao::{ 2 | Function, 3 | Op, 4 | Register, 5 | Var, 6 | }; 7 | 8 | const REGISTER_COUNT: usize = 32 + 32 + 1; 9 | fn register_index(reg: &Register) -> usize { 10 | match reg { 11 | Register::Gp(reg) => *reg as usize, 12 | Register::Fp(reg) => 32 + *reg as usize, 13 | Register::Csr(_) => 64, 14 | } 15 | } 16 | 17 | pub(crate) struct RegisterCachingPass { 18 | registers: [Option; REGISTER_COUNT], 19 | } 20 | 21 | impl RegisterCachingPass { 22 | #[allow(clippy::new_without_default)] 23 | pub(crate) fn new() -> Self { 24 | Self { 25 | registers: [None; REGISTER_COUNT], 26 | } 27 | } 28 | 29 | pub(crate) fn run(&mut self, func: &mut Function) -> Result<(), String> { 30 | for bb in func.cfg_mut().iter_basic_blocks_mut() { 31 | for reg in &mut self.registers { 32 | *reg = None; 33 | } 34 | 35 | bb.set_cursor(0); 36 | 37 | while let Some(op) = bb.cursor_op() { 38 | match op { 39 | Op::StoreRegister { 40 | reg, 41 | var, 42 | } => { 43 | self.registers[register_index(reg)] = Some(*var); 44 | }, 45 | Op::LoadRegister { 46 | var, 47 | reg, 48 | } => { 49 | if let Some(orig_var) = &self.registers[register_index(reg)] { 50 | bb.replace_op(Op::Copy { 51 | dst: *var, 52 | src: *orig_var, 53 | }); 54 | } 55 | }, 56 | _ => {}, 57 | } 58 | 59 | if !bb.move_cursor_forward() { 60 | break; 61 | } 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /squid/src/frontend/symbolization_passes/relax.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | frontend::{ 3 | ao::BasicBlock, 4 | ChunkContent, 5 | Elf, 6 | HasId, 7 | Pointer, 8 | }, 9 | riscv::register::GpRegister, 10 | }; 11 | 12 | pub(crate) struct HandleRelaxationPass {} 13 | 14 | impl HandleRelaxationPass { 15 | #[allow(clippy::new_without_default)] 16 | pub(crate) fn new() -> Self { 17 | Self {} 18 | } 19 | 20 | pub(crate) fn run(&mut self, elf: &mut Elf) -> Result<(), String> { 21 | for section in elf.iter_sections_mut() { 22 | if !section.perms().is_executable() { 23 | continue; 24 | } 25 | 26 | for symbol in section.iter_symbols_mut() { 27 | if symbol.private_name("load_gp").is_some() { 28 | let mut id = None; 29 | 30 | for chunk in symbol.iter_chunks() { 31 | assert!(id.is_none()); 32 | id = Some(chunk.id()); 33 | } 34 | 35 | let id = id.unwrap(); 36 | 37 | let chunk = symbol.chunk_mut(id).unwrap(); 38 | let addr = chunk.vaddr(); 39 | let ChunkContent::Code(func) = chunk.content_mut() else { unreachable!() }; 40 | 41 | func.cfg_mut().clear(); 42 | 43 | let mut bb = BasicBlock::at_vaddr(addr); 44 | let null = bb.load_pointer(Pointer::Null); 45 | bb.store_gp_register(GpRegister::gp, null).unwrap(); 46 | let ra = bb.load_gp_register(GpRegister::ra); 47 | bb.jump(ra).unwrap(); 48 | 49 | let id = func.cfg_mut().add_basic_block(bb); 50 | func.cfg_mut().set_entry(id); 51 | } 52 | } 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /squid/src/kernel/mod.rs: -------------------------------------------------------------------------------- 1 | //! Unstable. Use at your own risk. 2 | 3 | #![allow(missing_docs)] 4 | 5 | pub mod fs; 6 | pub mod linux; 7 | pub mod structs; 8 | -------------------------------------------------------------------------------- /squid/src/kernel/structs.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | #[repr(C)] 3 | pub struct Timespec { 4 | pub tv_sec: u64, 5 | pub tv_nsec: u64, 6 | } 7 | 8 | #[derive(Debug, Clone)] 9 | #[repr(C)] 10 | pub struct Stat { 11 | pub st_dev: u64, 12 | pub st_ino: u64, 13 | pub st_mode: u32, 14 | pub st_nlink: u32, 15 | pub st_uid: u32, 16 | pub st_gid: u32, 17 | pub st_rdev: u64, 18 | pub unknown: u64, 19 | pub st_size: u64, 20 | pub st_blksize: u64, 21 | pub st_blocks: u64, 22 | pub st_atime: Timespec, 23 | pub st_mtime: Timespec, 24 | pub st_ctime: Timespec, 25 | pub pad: u64, 26 | } 27 | -------------------------------------------------------------------------------- /squid/src/lib.rs: -------------------------------------------------------------------------------- 1 | //!

2 | //! 3 | //! `squid` is a RISC-V emulation library with features that make it a powerful tool for vulnerability research and fuzzing. 4 | //! 5 | //! Unlike other emulators, `squid` utilizes AOT instead of JIT compilation and allows you to rewrite your target's code before emulation. 6 | //! During runtime, you get full control over your target by handling all system calls and other events yourself. 7 | //! This makes it easy to create and combine new sanitizers and test programs for all kinds of vulnerabilities, not just memory corruptions. 8 | //! 9 | //! # Where to start 10 | //! Everything in `squid` starts with the [`Compiler`], so have a look at that. 11 | //! 12 | //! # Examples, a wiki and more... 13 | //! ...can be found in the [repository](https://github.com/fkie-cad/squid). 14 | //! 15 | //! # What is supported 16 | //! Binaries compiled with `squid`s own [RISC-V toolchain](https://github.com/fkie-cad/squid/blob/main/wiki/TOOLCHAIN.md) and this special set of flags: 17 | //! ``` 18 | //! -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= 19 | //! ``` 20 | //! 21 | //! # Features 22 | //! - `tui` (enabled by default): Enables animations and fancy loading graphics 23 | //! 24 | 25 | #![warn(missing_docs)] 26 | #![feature(btree_extract_if)] 27 | 28 | mod compiler; 29 | mod listing; 30 | mod logger; 31 | 32 | /* Squids interface: */ 33 | pub mod backends; 34 | pub mod event; 35 | pub mod frontend; 36 | pub mod kernel; 37 | pub mod passes; 38 | pub mod riscv; 39 | pub mod runtime; 40 | 41 | pub use compiler::{Loader, Compiler}; 42 | pub use logger::Logger; 43 | -------------------------------------------------------------------------------- /squid/src/listing.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use squid_ewe::{ 4 | Listing, 5 | ListingFunction, 6 | }; 7 | 8 | use crate::frontend::{ 9 | LoaderError, 10 | Symbol, 11 | }; 12 | 13 | pub(crate) struct ListingManager { 14 | listing: Option, 15 | } 16 | 17 | impl ListingManager { 18 | pub(crate) fn new>(path: P) -> Self { 19 | let path = format!("{}.{}", path.as_ref().display(), squid_ewe::EXTENSION); 20 | let path = Path::new(&path); 21 | Self { 22 | listing: if path.exists() { Some(Listing::from_file(path)) } else { None }, 23 | } 24 | } 25 | 26 | pub(crate) fn have_metadata(&self) -> bool { 27 | self.listing.is_some() 28 | } 29 | 30 | pub(crate) fn lookup_symbol(&self, symbol: &Symbol) -> Result, LoaderError> { 31 | if let Some(listing) = &self.listing { 32 | let mut funcs = Vec::new(); 33 | 34 | for private_name in symbol.private_names() { 35 | if !funcs.is_empty() { 36 | break; 37 | } 38 | 39 | funcs = listing.match_symbol(private_name.as_str(), Some(symbol.size()), symbol.file()); 40 | } 41 | 42 | for public_name in symbol.public_names() { 43 | if !funcs.is_empty() { 44 | break; 45 | } 46 | 47 | funcs = listing.match_symbol(public_name.as_str(), Some(symbol.size()), symbol.file()); 48 | } 49 | 50 | match funcs.len() { 51 | 0 => Ok(None), 52 | 1 => Ok(Some(funcs[0])), 53 | _ => Err(LoaderError::EweError(format!( 54 | "Multiple functions in listing for symbol {:#x}", 55 | symbol.vaddr() 56 | ))), 57 | } 58 | } else { 59 | Ok(None) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /squid/src/logger.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "tui")] 2 | use core::time::Duration; 3 | use std::{ 4 | borrow::Cow, 5 | fmt::Display, 6 | }; 7 | 8 | use colored::Colorize; 9 | use indicatif::{ 10 | ProgressBar, 11 | ProgressStyle, 12 | }; 13 | 14 | /// The Logger is a helper struct that displays log messages to the terminal. 15 | /// 16 | /// If the feature `tui` is activated, it also displays some nice TUI animations. 17 | /// Use the functions [`Logger::info`], [`Logger::warning`], [`Logger::error`] to emit log messages 18 | /// at the corresponding log levels. 19 | pub struct Logger { 20 | bar: ProgressBar, 21 | running: bool, 22 | prefix: Option, 23 | } 24 | 25 | #[cfg(feature = "tui")] 26 | const ANIMATION: &[&str; 9] = &[". ", ".. ", "...", " ..", " .", " ..", "...", "..", ""]; 27 | 28 | #[cfg(not(feature = "tui"))] 29 | const ANIMATION: &[&str; 2] = &["...", ""]; 30 | 31 | impl Logger { 32 | pub(crate) fn spinner() -> Self { 33 | let bar = ProgressBar::new_spinner(); 34 | bar.set_style( 35 | ProgressStyle::with_template("{prefix:.magenta/red} {msg} {spinner}").unwrap().tick_strings(ANIMATION), 36 | ); 37 | bar.set_prefix("[🦑]"); 38 | 39 | Self { 40 | bar, 41 | running: false, 42 | prefix: None, 43 | } 44 | } 45 | 46 | pub(crate) fn set_prefix>(&mut self, prefix: S) { 47 | self.prefix = Some(prefix.into()); 48 | } 49 | 50 | pub(crate) fn clear_prefix(&mut self) { 51 | self.prefix = None; 52 | } 53 | 54 | pub(crate) fn set_title(&mut self, title: impl Into>) { 55 | #[cfg(feature = "tui")] 56 | if !self.running { 57 | self.bar.enable_steady_tick(Duration::from_millis(100)); 58 | self.running = true; 59 | } 60 | self.bar.set_message(title.into()); 61 | } 62 | 63 | fn stop(&mut self) { 64 | if self.running { 65 | self.running = false; 66 | self.bar.finish_and_clear(); 67 | } 68 | } 69 | 70 | fn emit>(&self, level: L, msg: S) { 71 | if let Some(prefix) = &self.prefix { 72 | self.bar.println(format!("{} {}{}{} {}", level, "(".bold(), prefix.bold(), ")".bold(), msg.as_ref())) 73 | } else { 74 | self.bar.println(format!("{} {}", level, msg.as_ref())); 75 | } 76 | } 77 | 78 | /// Emit a log message with log level "INFO" 79 | pub fn info>(&self, msg: S) { 80 | self.emit("[🦑::INFO]".blue().bold(), msg); 81 | } 82 | 83 | /// Emit a log message with log level "WARN" 84 | pub fn warning>(&self, msg: S) { 85 | self.emit("[🦑::WARN]".yellow().bold(), msg); 86 | } 87 | 88 | /// Emit a log message with log level "DEBUG" 89 | pub fn debug>(&self, _msg: S) { 90 | #[cfg(debug_assertions)] 91 | { 92 | self.emit("[🦑::DEBUG]".black().on_white(), _msg); 93 | } 94 | } 95 | 96 | /// Emit a log message with log level "ERROR" 97 | pub fn error>(&self, msg: S) { 98 | self.emit("[🦑::ERROR]".red().bold(), msg); 99 | } 100 | } 101 | 102 | impl Drop for Logger { 103 | fn drop(&mut self) { 104 | self.stop(); 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::*; 111 | 112 | #[test] 113 | #[ignore] 114 | fn test_style() { 115 | let mut logger = Logger::spinner(); 116 | logger.set_title("TITLE HERE"); 117 | logger.info("info"); 118 | logger.warning("warning"); 119 | logger.debug("debug"); 120 | logger.error("error"); 121 | 122 | std::thread::sleep(std::time::Duration::from_secs(5)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /squid/src/passes/breakpoint.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::{ 6 | event::EventPool, 7 | frontend::{ 8 | ao::{ 9 | events::EVENT_BREAKPOINT, 10 | BasicBlock, 11 | Edge, 12 | Function, 13 | Op, 14 | }, 15 | ChunkContent, 16 | ProcessImage, 17 | VAddr, 18 | }, 19 | logger::Logger, 20 | passes::Pass, 21 | }; 22 | 23 | /// The error type of the [`BreakpointPass`] 24 | #[derive(Error, Debug)] 25 | #[error("BreakpointPassError")] 26 | pub struct BreakpointPassError; 27 | 28 | /// The BreakpointPass allows you to insert breakpoints into the application. 29 | pub struct BreakpointPass { 30 | private_names: HashSet, 31 | public_names: HashSet, 32 | addrs: HashSet, 33 | all: bool, 34 | } 35 | 36 | impl BreakpointPass { 37 | /// Create a new BreakpointPass 38 | #[allow(clippy::new_without_default)] 39 | pub fn new() -> Self { 40 | Self { 41 | private_names: HashSet::new(), 42 | public_names: HashSet::new(), 43 | addrs: HashSet::new(), 44 | all: false, 45 | } 46 | } 47 | 48 | /// Break on all basic blocks 49 | pub fn all(&mut self) -> &mut Self { 50 | self.all = true; 51 | self 52 | } 53 | 54 | /// Break on functions with the given private name 55 | pub fn private_name>(&mut self, name: S) -> &mut Self { 56 | self.private_names.insert(name.into()); 57 | self 58 | } 59 | 60 | /// Break on functions with the given public name 61 | pub fn public_name>(&mut self, name: S) -> &mut Self { 62 | self.public_names.insert(name.into()); 63 | self 64 | } 65 | 66 | /// Break at the provided address 67 | pub fn address(&mut self, addr: VAddr) -> &mut Self { 68 | self.addrs.insert(addr); 69 | self 70 | } 71 | 72 | fn instrument(&self, func: &mut Function, event_pool: &EventPool) { 73 | let breakpoint = event_pool.get_event(EVENT_BREAKPOINT).unwrap(); 74 | 75 | let old_entry = func.cfg().entry(); 76 | let entry_vaddr = func.cfg().basic_block(old_entry).unwrap().vaddr(); 77 | let mut entry_instr = None; 78 | 79 | for op in func.cfg().basic_block(old_entry).unwrap().ops() { 80 | if let Op::NextInstruction { vaddr } = op { 81 | entry_instr = Some(*vaddr); 82 | break; 83 | } 84 | } 85 | 86 | let mut bb = BasicBlock::new(); 87 | 88 | if let Some(entry_vaddr) = entry_vaddr { 89 | bb.set_vaddr(entry_vaddr); 90 | } 91 | 92 | if let Some(entry_instr) = entry_instr { 93 | bb.next_instruction(entry_instr); 94 | } 95 | 96 | bb.fire_event(breakpoint); 97 | 98 | bb.add_edge(Edge::Next(old_entry)); 99 | 100 | let id = func.cfg_mut().add_basic_block(bb); 101 | func.cfg_mut().set_entry(id); 102 | 103 | assert_ne!(old_entry, id); 104 | } 105 | } 106 | 107 | impl Pass for BreakpointPass { 108 | type Error = BreakpointPassError; 109 | 110 | fn name(&self) -> String { 111 | "BreakpointPass".to_string() 112 | } 113 | 114 | fn run( 115 | &mut self, 116 | image: &mut ProcessImage, 117 | event_pool: &mut EventPool, 118 | logger: &Logger, 119 | ) -> Result<(), Self::Error> { 120 | let mut count = 0; 121 | 122 | for elf in image.iter_elfs_mut() { 123 | for section in elf.iter_sections_mut() { 124 | for symbol in section.iter_symbols_mut() { 125 | let mut found = self.all; 126 | 127 | /* Check if it matches a public name */ 128 | if !found { 129 | for name in &self.public_names { 130 | if symbol.public_name(name).is_some() { 131 | found = true; 132 | break; 133 | } 134 | } 135 | } 136 | 137 | /* Check if it matches a private name */ 138 | if !found { 139 | for name in &self.private_names { 140 | if symbol.private_name(name).is_some() { 141 | found = true; 142 | break; 143 | } 144 | } 145 | } 146 | 147 | for chunk in symbol.iter_chunks_mut() { 148 | /* Check if the address matches */ 149 | if !found { 150 | found |= self.addrs.contains(&chunk.vaddr()); 151 | } 152 | 153 | if found { 154 | if let ChunkContent::Code(func) = chunk.content_mut() { 155 | self.instrument(func, event_pool); 156 | count += 1; 157 | } 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | logger.info(format!("Set {} breakpoints", count)); 165 | 166 | Ok(()) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /squid/src/passes/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains the passes that are provided by `squid` 2 | 3 | mod asan; 4 | mod breakpoint; 5 | mod dot; 6 | mod pass; 7 | mod verify; 8 | 9 | pub use asan::AsanPass; 10 | pub use breakpoint::{ 11 | BreakpointPass, 12 | BreakpointPassError, 13 | }; 14 | pub use dot::{ 15 | FunctionDOTPass, 16 | ImageDOTPass, 17 | }; 18 | pub use pass::{ 19 | NoPassError, 20 | Pass, 21 | }; 22 | pub(crate) use verify::VerifyerPass; 23 | pub use verify::VerifyerPassError; 24 | -------------------------------------------------------------------------------- /squid/src/passes/pass.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::{ 4 | event::EventPool, 5 | frontend::ProcessImage, 6 | logger::Logger, 7 | }; 8 | 9 | /// This type can be used as a `Pass::Error` to signal 10 | /// that a pass never fails. 11 | #[derive(Error, Debug)] 12 | #[error("This pass is supposed to not throw any errors")] 13 | pub struct NoPassError; 14 | 15 | /// A pass in `squid` is any type that implements this trait. 16 | /// 17 | /// Passes can be used to inspect or modify the process image. 18 | pub trait Pass { 19 | /// The error that might be returned by the pass 20 | type Error: std::error::Error; 21 | 22 | /// The name of the pass (displayed on the terminal) 23 | fn name(&self) -> String; 24 | 25 | /// Run the pass. 26 | /// 27 | /// # Arguments 28 | /// 1. `image`: The process image that contains all code and data of the target application and all its dependencies 29 | /// 2. `event_pool`: The event pool that manages all events that can be thrown by the application 30 | /// 3. `logger`: A helper struct that can display log messages at different log levels 31 | fn run(&mut self, image: &mut ProcessImage, event_pool: &mut EventPool, logger: &Logger) 32 | -> Result<(), Self::Error>; 33 | } 34 | -------------------------------------------------------------------------------- /squid/src/riscv/fflags.rs: -------------------------------------------------------------------------------- 1 | //! The flags that can be set in the `fflags` CSR. 2 | 3 | /// Inexact 4 | pub const NX: u32 = 1 << 0; 5 | 6 | /// Underflow 7 | pub const UF: u32 = 1 << 1; 8 | 9 | /// Overflow 10 | pub const OF: u32 = 1 << 2; 11 | 12 | /// Divide by zero 13 | pub const DZ: u32 = 1 << 3; 14 | 15 | /// Invalid operation 16 | pub const NV: u32 = 1 << 4; 17 | -------------------------------------------------------------------------------- /squid/src/riscv/ieee754.rs: -------------------------------------------------------------------------------- 1 | //! Contains types related to the IEEE754 usage of the RISC-V ISA. 2 | 3 | use std::ops::{ 4 | AddAssign, 5 | SubAssign, 6 | }; 7 | 8 | use num_traits::{ 9 | bounds::Bounded, 10 | cast::NumCast, 11 | float::Float, 12 | }; 13 | 14 | pub(crate) const NAN_BOX: u64 = 0xFFFFFFFF00000000; 15 | pub(crate) const SINGLE_NAN: u32 = 0x7fc00000; 16 | pub(crate) const DOUBLE_NAN: u64 = 0x7ff8000000000000; 17 | 18 | pub(crate) trait RiscvFloat: Float { 19 | fn is_half(&self) -> bool; 20 | fn is_odd(&self) -> bool; 21 | fn is_signaling(&self) -> bool; 22 | fn riscv_nan() -> Self; 23 | 24 | fn is_special(&self) -> bool { 25 | self.is_infinite() || self.is_nan() || *self == Self::max_value() || *self == Self::min_value() 26 | } 27 | } 28 | 29 | impl RiscvFloat for f32 { 30 | fn is_half(&self) -> bool { 31 | self.abs() == 0.5f32 32 | } 33 | 34 | fn is_odd(&self) -> bool { 35 | (self % 2.0f32).abs() != 0.0f32 36 | } 37 | 38 | fn is_signaling(&self) -> bool { 39 | (self.to_bits() & (1u32 << 22)) == 0 40 | } 41 | 42 | fn riscv_nan() -> Self { 43 | Self::from_bits(SINGLE_NAN) 44 | } 45 | } 46 | 47 | impl RiscvFloat for f64 { 48 | fn is_half(&self) -> bool { 49 | self.abs() == 0.5f64 50 | } 51 | 52 | fn is_odd(&self) -> bool { 53 | (self % 2.0f64).abs() != 0.0f64 54 | } 55 | 56 | fn is_signaling(&self) -> bool { 57 | (self.to_bits() & (1u64 << 51)) == 0 58 | } 59 | 60 | fn riscv_nan() -> Self { 61 | Self::from_bits(DOUBLE_NAN) 62 | } 63 | } 64 | 65 | /// Round to nearest, tie to even 66 | pub(crate) fn round_nte(mut x: T) -> T { 67 | if x.is_special() { 68 | return x; 69 | } 70 | 71 | let (trunk, fract) = (x.trunc(), x.fract()); 72 | 73 | if fract.is_half() { 74 | if trunk.is_odd() { 75 | x = (x + fract).copysign(x); 76 | } else { 77 | x = (x - fract).copysign(x); 78 | } 79 | } 80 | 81 | x.round() 82 | } 83 | 84 | /// Round towards zero 85 | pub(crate) fn round_tz(x: T) -> T { 86 | if x.is_special() { 87 | return x; 88 | } 89 | 90 | if x.is_sign_positive() { 91 | x.floor() 92 | } else { 93 | x.ceil() 94 | } 95 | } 96 | 97 | /// Round down 98 | pub(crate) fn round_dn(x: T) -> T { 99 | if x.is_special() { 100 | return x; 101 | } 102 | 103 | x.floor() 104 | } 105 | 106 | /// Round up 107 | pub(crate) fn round_up(x: T) -> T { 108 | if x.is_special() { 109 | return x; 110 | } 111 | 112 | x.ceil() 113 | } 114 | 115 | /// Round to nearest, tie to max magnitude 116 | pub(crate) fn round_nmm(mut x: T) -> T { 117 | if x.is_special() { 118 | return x; 119 | } 120 | 121 | let fract = x.fract(); 122 | 123 | if fract.is_half() { 124 | x += fract; 125 | } 126 | 127 | x.round() 128 | } 129 | 130 | pub(crate) fn add(a: T, b: T) -> T 131 | where 132 | T: RiscvFloat, 133 | { 134 | if a.is_nan() || b.is_nan() { 135 | T::riscv_nan() 136 | } else if a.is_infinite() { 137 | if b.is_infinite() && (a.is_sign_positive() ^ b.is_sign_positive()) { 138 | T::riscv_nan() 139 | } else { 140 | a 141 | } 142 | } else if b.is_infinite() { 143 | if a.is_infinite() && (a.is_sign_positive() ^ b.is_sign_positive()) { 144 | T::riscv_nan() 145 | } else { 146 | b 147 | } 148 | } else { 149 | a + b 150 | } 151 | } 152 | 153 | pub(crate) fn sub(a: T, b: T) -> T { 154 | if a.is_nan() || b.is_nan() { 155 | T::riscv_nan() 156 | } else if a.is_infinite() && b.is_infinite() { 157 | if a.is_sign_positive() ^ b.is_sign_positive() { 158 | a 159 | } else { 160 | T::riscv_nan() 161 | } 162 | } else { 163 | a - b 164 | } 165 | } 166 | 167 | pub(crate) fn mul(a: T, b: T) -> T { 168 | if (a.is_nan() || b.is_nan()) || (a.is_infinite() && b.is_zero()) || (a.is_zero() && b.is_infinite()) { 169 | T::riscv_nan() 170 | } else { 171 | a * b 172 | } 173 | } 174 | 175 | pub(crate) fn div(a: T, b: T) -> T { 176 | if a.is_nan() || b.is_nan() { 177 | T::riscv_nan() 178 | } else if b.is_infinite() { 179 | if a.is_infinite() { 180 | T::riscv_nan() 181 | } else if a.is_sign_positive() ^ b.is_sign_positive() { 182 | -T::zero() 183 | } else { 184 | T::zero() 185 | } 186 | } else if a.is_infinite() { 187 | if a.is_sign_positive() ^ b.is_sign_positive() { 188 | T::neg_infinity() 189 | } else { 190 | T::infinity() 191 | } 192 | } else if b.is_zero() { 193 | if a.is_zero() { 194 | T::riscv_nan() 195 | } else if a.is_sign_positive() ^ b.is_sign_positive() { 196 | T::neg_infinity() 197 | } else { 198 | T::infinity() 199 | } 200 | } else { 201 | a / b 202 | } 203 | } 204 | 205 | pub(crate) fn min(a: T, b: T) -> T { 206 | if a.is_nan() || b.is_nan() { 207 | if a.is_nan() && b.is_nan() { 208 | T::riscv_nan() 209 | } else if b.is_nan() { 210 | a 211 | } else { 212 | b 213 | } 214 | } else if a.is_zero() && b.is_zero() && a.is_sign_positive() != b.is_sign_positive() { 215 | -T::zero() 216 | } else { 217 | a.min(b) 218 | } 219 | } 220 | 221 | pub(crate) fn max(a: T, b: T) -> T { 222 | if a.is_nan() || b.is_nan() { 223 | if a.is_nan() && b.is_nan() { 224 | T::riscv_nan() 225 | } else if a.is_nan() { 226 | b 227 | } else { 228 | a 229 | } 230 | } else if a.is_zero() && b.is_zero() && a.is_sign_positive() != b.is_sign_positive() { 231 | T::zero() 232 | } else { 233 | a.max(b) 234 | } 235 | } 236 | 237 | pub(crate) fn classify(a: T) -> u64 { 238 | let mut mask: u64 = 0; 239 | 240 | if a.is_nan() { 241 | if a.is_signaling() { 242 | mask |= 1 << 8; 243 | } else { 244 | mask |= 1 << 9; 245 | } 246 | } else if a.is_sign_negative() { 247 | if a.is_infinite() { 248 | mask |= 1 << 0; 249 | } else if a.is_subnormal() { 250 | mask |= 1 << 2; 251 | } else if a.is_zero() { 252 | mask |= 1 << 3; 253 | } else { 254 | mask |= 1 << 1; 255 | } 256 | } else if a.is_infinite() { 257 | mask |= 1 << 7; 258 | } else if a.is_subnormal() { 259 | mask |= 1 << 5; 260 | } else if a.is_zero() { 261 | mask |= 1 << 4; 262 | } else { 263 | mask |= 1 << 6; 264 | } 265 | 266 | mask 267 | } 268 | 269 | pub(crate) fn convert(f: F) -> I 270 | where 271 | F: RiscvFloat, 272 | I: Bounded + NumCast, 273 | { 274 | if f.is_nan() { 275 | I::max_value() 276 | } else if f.is_infinite() { 277 | if f.is_sign_positive() { 278 | I::max_value() 279 | } else { 280 | I::min_value() 281 | } 282 | } else if let Some(i) = I::from(f) { 283 | i 284 | } else if f.is_sign_positive() { 285 | I::max_value() 286 | } else { 287 | I::min_value() 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /squid/src/riscv/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides constants and types that are related to the RISC-V ISA. 2 | 3 | pub(crate) mod ieee754; 4 | pub(crate) mod instr; 5 | 6 | pub mod fflags; 7 | pub mod register; 8 | pub mod rm; 9 | pub mod syscalls; 10 | -------------------------------------------------------------------------------- /squid/src/riscv/register.rs: -------------------------------------------------------------------------------- 1 | //! Contains everything related to RISC-V registers 2 | 3 | #![allow(non_upper_case_globals)] 4 | 5 | mod gp { 6 | pub(super) const zero: usize = 0; 7 | pub(super) const ra: usize = 1; 8 | pub(super) const sp: usize = 2; 9 | pub(super) const gp: usize = 3; 10 | pub(super) const tp: usize = 4; 11 | pub(super) const t0: usize = 5; 12 | pub(super) const t1: usize = 6; 13 | pub(super) const t2: usize = 7; 14 | pub(super) const s0: usize = 8; 15 | pub(super) const s1: usize = 9; 16 | pub(super) const a0: usize = 10; 17 | pub(super) const a1: usize = 11; 18 | pub(super) const a2: usize = 12; 19 | pub(super) const a3: usize = 13; 20 | pub(super) const a4: usize = 14; 21 | pub(super) const a5: usize = 15; 22 | pub(super) const a6: usize = 16; 23 | pub(super) const a7: usize = 17; 24 | pub(super) const s2: usize = 18; 25 | pub(super) const s3: usize = 19; 26 | pub(super) const s4: usize = 20; 27 | pub(super) const s5: usize = 21; 28 | pub(super) const s6: usize = 22; 29 | pub(super) const s7: usize = 23; 30 | pub(super) const s8: usize = 24; 31 | pub(super) const s9: usize = 25; 32 | pub(super) const s10: usize = 26; 33 | pub(super) const s11: usize = 27; 34 | pub(super) const t3: usize = 28; 35 | pub(super) const t4: usize = 29; 36 | pub(super) const t5: usize = 30; 37 | pub(super) const t6: usize = 31; 38 | } 39 | 40 | mod fp { 41 | pub(super) const ft0: usize = 0; 42 | pub(super) const ft1: usize = 1; 43 | pub(super) const ft2: usize = 2; 44 | pub(super) const ft3: usize = 3; 45 | pub(super) const ft4: usize = 4; 46 | pub(super) const ft5: usize = 5; 47 | pub(super) const ft6: usize = 6; 48 | pub(super) const ft7: usize = 7; 49 | pub(super) const fs0: usize = 8; 50 | pub(super) const fs1: usize = 9; 51 | pub(super) const fa0: usize = 10; 52 | pub(super) const fa1: usize = 11; 53 | pub(super) const fa2: usize = 12; 54 | pub(super) const fa3: usize = 13; 55 | pub(super) const fa4: usize = 14; 56 | pub(super) const fa5: usize = 15; 57 | pub(super) const fa6: usize = 16; 58 | pub(super) const fa7: usize = 17; 59 | pub(super) const fs2: usize = 18; 60 | pub(super) const fs3: usize = 19; 61 | pub(super) const fs4: usize = 20; 62 | pub(super) const fs5: usize = 21; 63 | pub(super) const fs6: usize = 22; 64 | pub(super) const fs7: usize = 23; 65 | pub(super) const fs8: usize = 24; 66 | pub(super) const fs9: usize = 25; 67 | pub(super) const fs10: usize = 26; 68 | pub(super) const fs11: usize = 27; 69 | pub(super) const ft8: usize = 28; 70 | pub(super) const ft9: usize = 29; 71 | pub(super) const ft10: usize = 30; 72 | pub(super) const ft11: usize = 31; 73 | } 74 | 75 | mod csr { 76 | pub(super) const fflags: usize = 1; 77 | pub(super) const frm: usize = 2; 78 | pub(super) const fcsr: usize = 3; 79 | } 80 | 81 | /// The general purpose registers of the RISC-V ISA 82 | #[allow(missing_docs)] 83 | #[allow(non_camel_case_types)] 84 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 85 | #[repr(usize)] 86 | pub enum GpRegister { 87 | zero = gp::zero, 88 | ra = gp::ra, 89 | sp = gp::sp, 90 | gp = gp::gp, 91 | tp = gp::tp, 92 | t0 = gp::t0, 93 | t1 = gp::t1, 94 | t2 = gp::t2, 95 | s0 = gp::s0, 96 | s1 = gp::s1, 97 | a0 = gp::a0, 98 | a1 = gp::a1, 99 | a2 = gp::a2, 100 | a3 = gp::a3, 101 | a4 = gp::a4, 102 | a5 = gp::a5, 103 | a6 = gp::a6, 104 | a7 = gp::a7, 105 | s2 = gp::s2, 106 | s3 = gp::s3, 107 | s4 = gp::s4, 108 | s5 = gp::s5, 109 | s6 = gp::s6, 110 | s7 = gp::s7, 111 | s8 = gp::s8, 112 | s9 = gp::s9, 113 | s10 = gp::s10, 114 | s11 = gp::s11, 115 | t3 = gp::t3, 116 | t4 = gp::t4, 117 | t5 = gp::t5, 118 | t6 = gp::t6, 119 | } 120 | 121 | impl GpRegister { 122 | pub(crate) fn from_usize(number: usize) -> Self { 123 | assert!(number < 32); 124 | unsafe { std::mem::transmute::(number) } 125 | } 126 | } 127 | 128 | /// The floating point register of the RISC-V ISA 129 | #[allow(missing_docs)] 130 | #[allow(non_camel_case_types)] 131 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 132 | #[repr(usize)] 133 | pub enum FpRegister { 134 | ft0 = fp::ft0, 135 | ft1 = fp::ft1, 136 | ft2 = fp::ft2, 137 | ft3 = fp::ft3, 138 | ft4 = fp::ft4, 139 | ft5 = fp::ft5, 140 | ft6 = fp::ft6, 141 | ft7 = fp::ft7, 142 | fs0 = fp::fs0, 143 | fs1 = fp::fs1, 144 | fa0 = fp::fa0, 145 | fa1 = fp::fa1, 146 | fa2 = fp::fa2, 147 | fa3 = fp::fa3, 148 | fa4 = fp::fa4, 149 | fa5 = fp::fa5, 150 | fa6 = fp::fa6, 151 | fa7 = fp::fa7, 152 | fs2 = fp::fs2, 153 | fs3 = fp::fs3, 154 | fs4 = fp::fs4, 155 | fs5 = fp::fs5, 156 | fs6 = fp::fs6, 157 | fs7 = fp::fs7, 158 | fs8 = fp::fs8, 159 | fs9 = fp::fs9, 160 | fs10 = fp::fs10, 161 | fs11 = fp::fs11, 162 | ft8 = fp::ft8, 163 | ft9 = fp::ft9, 164 | ft10 = fp::ft10, 165 | ft11 = fp::ft11, 166 | } 167 | 168 | impl FpRegister { 169 | pub(crate) fn from_usize(number: usize) -> Self { 170 | assert!(number < 32); 171 | unsafe { std::mem::transmute::(number) } 172 | } 173 | } 174 | 175 | /// The control/status registers of the RISC-V ISA 176 | #[allow(missing_docs)] 177 | #[allow(non_camel_case_types)] 178 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 179 | #[repr(usize)] 180 | pub enum CsrRegister { 181 | fflags = csr::fflags, 182 | frm = csr::frm, 183 | fcsr = csr::fcsr, 184 | } 185 | 186 | impl CsrRegister { 187 | pub(crate) fn from_usize(number: usize) -> Self { 188 | match number { 189 | csr::fflags => CsrRegister::fflags, 190 | csr::frm => CsrRegister::frm, 191 | csr::fcsr => CsrRegister::fcsr, 192 | _ => panic!(), 193 | } 194 | } 195 | } 196 | 197 | /* syscall related registers */ 198 | /// The general purpose register that holds the syscall number 199 | pub const syscall_number: GpRegister = GpRegister::a7; 200 | /// The general purpose registers that hold the syscall arguments 201 | pub const syscall_args: [GpRegister; 6] = 202 | [GpRegister::a0, GpRegister::a1, GpRegister::a2, GpRegister::a3, GpRegister::a4, GpRegister::a5]; 203 | /// The general purpose register that holds the return value of a syscall 204 | pub const syscall_ret: GpRegister = GpRegister::a0; 205 | -------------------------------------------------------------------------------- /squid/src/riscv/rm.rs: -------------------------------------------------------------------------------- 1 | //! The rounding modes of the RISC-V ISA that might appear in the `frm` CSR or in floating point instructions 2 | 3 | /// Round to nearest, ties to even 4 | pub const RNE: u64 = 0b000; 5 | /// Round towards zero 6 | pub const RTZ: u64 = 0b001; 7 | /// Round towards -infinity 8 | pub const RDN: u64 = 0b010; 9 | /// Round towards +infinity 10 | pub const RUP: u64 = 0b011; 11 | /// Round to nearest, ties to max magnitude 12 | pub const RMM: u64 = 0b100; 13 | 14 | /// Only valid in instructions but not in fcsr 15 | pub const DYNAMIC: u64 = 0b111; 16 | -------------------------------------------------------------------------------- /squid/src/runtime.rs: -------------------------------------------------------------------------------- 1 | //! Contains the [`Runtime`] trait. 2 | 3 | use crate::{ 4 | frontend::VAddr, 5 | riscv::register::{ 6 | CsrRegister, 7 | FpRegister, 8 | GpRegister, 9 | }, 10 | }; 11 | 12 | /// Snapshots are referred to by IDs of this type 13 | pub type SnapshotId = usize; 14 | 15 | /// This trait contains the minimum behavior that is expected from any runtime. 16 | pub trait Runtime { 17 | /// Each runtime has its corresponding error type 18 | type Error: std::error::Error; 19 | 20 | /// The Event is the return value of the [`Runtime::run`] function 21 | type Event; 22 | 23 | /* Register I/O */ 24 | /// Set the program counter to the specified address 25 | fn set_pc(&mut self, pc: VAddr); 26 | /// Get the program counter of the program 27 | fn get_pc(&self) -> VAddr; 28 | /// Store a value into a general puporse register 29 | fn set_gp_register(&mut self, register: GpRegister, value: u64); 30 | /// Retrieve the value of a general purpose register 31 | fn get_gp_register(&self, register: GpRegister) -> u64; 32 | /// Store a value into a floating point register 33 | fn set_fp_register(&mut self, register: FpRegister, value: f64); 34 | /// Retrieve the value of a floating point register 35 | fn get_fp_register(&self, register: FpRegister) -> f64; 36 | /// Store a value into one of the control/status registers 37 | fn set_csr_register(&mut self, register: CsrRegister, value: u64); 38 | /// Retrieve the value of one of the control/status registers 39 | fn get_csr_register(&self, register: CsrRegister) -> u64; 40 | 41 | /* Interact with the code */ 42 | /// Execute the program starting at the current program counter until an event is thrown 43 | fn run(&mut self) -> Result; 44 | 45 | /* Snapshot handling */ 46 | /// Check if a snapshot with the given ID is available 47 | fn has_snapshot(&self, id: SnapshotId) -> bool; 48 | /// Take a snapshot of the current program state and assign it the given ID. 49 | /// If a snapshot with the same ID has already been created, it is overwritten. 50 | fn take_snapshot(&mut self, id: SnapshotId); 51 | /// Given a snapshot ID, restore the corresponding snapshot 52 | fn restore_snapshot(&mut self, id: SnapshotId) -> Result<(), Self::Error>; 53 | /// Delete the snapshot with the given ID from the snapshot store 54 | fn delete_snapshot(&mut self, id: SnapshotId) -> Result<(), Self::Error>; 55 | 56 | /* Event channel I/O */ 57 | /// Get read-only access to the data in the event channel 58 | fn event_channel(&self) -> &[u64]; 59 | /// Get write-access to the event channel. `size` determines how many elements you want to place into the event channel. 60 | fn event_channel_mut(&mut self, size: usize) -> Result<&mut [u64], Self::Error>; 61 | 62 | /* Memory I/O */ 63 | /// Return the memory contents at the given address as a dword (8 bytes) 64 | fn load_dword(&self, address: VAddr) -> Result; 65 | /// Return the memory contents at the given address as a word (4 bytes) 66 | fn load_word(&self, address: VAddr) -> Result; 67 | /// Return the memory contents at the given address as an hword (2 bytes) 68 | fn load_hword(&self, address: VAddr) -> Result; 69 | /// Return the memory contents at the given address as a byte 70 | fn load_byte(&self, address: VAddr) -> Result; 71 | /// Return the memory contents at the given address as an array of bytes with size `size` 72 | fn load_slice(&self, address: VAddr, size: usize) -> Result<&[u8], Self::Error>; 73 | /// Store the given value at the given address as a byte 74 | fn store_byte(&mut self, address: VAddr, value: u8) -> Result<(), Self::Error>; 75 | /// Store the given value at the given address as a hword (2 bytes) 76 | fn store_hword(&mut self, address: VAddr, value: u16) -> Result<(), Self::Error>; 77 | /// Store the given value at the given address as a word (4 bytes) 78 | fn store_word(&mut self, address: VAddr, value: u32) -> Result<(), Self::Error>; 79 | /// Store the given value at the given address as a dword (8 bytes) 80 | fn store_dword(&mut self, address: VAddr, value: u64) -> Result<(), Self::Error>; 81 | /// Store the given byte array at the given address 82 | fn store_slice>(&mut self, address: VAddr, value: S) -> Result<(), Self::Error>; 83 | /// Retreive a NUL-terminated string at the given address. 84 | /// The NUL terminator must not be included in the return value but exists in memory. 85 | fn load_string(&self, address: VAddr) -> Result<&[u8], Self::Error>; 86 | /// Store the provided string as is at the given address. A NUL-terminator must be added by the implementation. 87 | fn store_string>(&mut self, address: VAddr, value: S) -> Result<(), Self::Error>; 88 | } 89 | -------------------------------------------------------------------------------- /squid_ewe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squid_ewe" 3 | readme = "README.md" 4 | description = "A helper tool for squid that extracts CFG metadata from C code" 5 | version.workspace = true 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | 10 | [dev-dependencies] 11 | goblin = { version = "0.6", features = ["std", "elf32", "elf64"]} 12 | -------------------------------------------------------------------------------- /squid_ewe/README.md: -------------------------------------------------------------------------------- 1 | # ewe 2 | 3 | `ewe` is a compiler wrapper toolset that extracts metadata from C 4 | code and stores it in addition to the binaries as `.ewe` files. 5 | 6 | Its primary purpose is to tackle the information loss problem during compilation 7 | and reconstruct basic block boundaries in machine code. 8 | This enables CFG reconstruction from C code making use of the `goto*` extension. 9 | 10 | ## Toolchain 11 | The `ewe` tools are part of squid's toolchain and can be found in the `/ewe/` directory 12 | inside the docker container. 13 | The following tools are available: 14 | 15 | - `/ewe/gcc`: A wrapper around `riscv64-unknown-linux-gnu-gcc` 16 | - `/ewe/as`: A wrapper around `riscv64-unknown-linux-gnu-as` 17 | - `/ewe/ar`: A wrapper around `riscv64-unknown-linux-gnu-ar` 18 | - `/ewe/ld`: A wrapper around `riscv64-unknown-linux-gnu-ld` 19 | 20 | Use these programs instead of their RISC-V counterparts whenever your target uses `goto*` statements. 21 | 22 | ## Integration into squid 23 | `squid` automatically detects if the binaries it loads have a corresponding `.ewe` file and uses that 24 | for CFG reconstruction. 25 | It is important that the `.ewe` files follow the following convention: For a binary `path/to/binary.ext` that is loaded 26 | by `squid` the `.ewe` file must be named `path/to/binary.ext.ewe`. So, the `.ewe` extension is appended to the filename 27 | in order to signal that this `.ewe` file belongs to this binary. 28 | -------------------------------------------------------------------------------- /squid_ewe/src/bin/ar.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | process::{ 4 | exit, 5 | Command, 6 | }, 7 | }; 8 | 9 | use squid_ewe::{ 10 | container::*, 11 | env::*, 12 | }; 13 | 14 | fn build_cmd() -> Command { 15 | let wrapper_path = env_value(EWE_WRAPPER, EWE_WRAPPER_PATH); 16 | let mut cmd = Command::new(wrapper_path); 17 | cmd.arg(env_value(RISCV_AR, RISCV_AR_PATH)); 18 | cmd.args(env::args().skip(1)); 19 | cmd 20 | } 21 | 22 | fn main() -> ! { 23 | let status = build_cmd().envs(env::vars()).status().unwrap(); 24 | 25 | if let Some(code) = status.code() { 26 | exit(code); 27 | } else { 28 | exit(-1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /squid_ewe/src/bin/as.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | process::{ 4 | exit, 5 | Command, 6 | }, 7 | }; 8 | 9 | use squid_ewe::{ 10 | container::*, 11 | env::*, 12 | }; 13 | 14 | fn build_cmd() -> Command { 15 | let wrapper_path = env_value(EWE_WRAPPER, EWE_WRAPPER_PATH); 16 | let mut cmd = Command::new(wrapper_path); 17 | cmd.arg(env_value(RISCV_AS, RISCV_AS_PATH)); 18 | cmd.args(env::args().skip(1)); 19 | cmd 20 | } 21 | 22 | fn main() -> ! { 23 | let status = build_cmd().envs(env::vars()).status().unwrap(); 24 | 25 | if let Some(code) = status.code() { 26 | exit(code); 27 | } else { 28 | exit(-1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /squid_ewe/src/bin/gcc.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | process::{ 4 | exit, 5 | Command, 6 | }, 7 | }; 8 | 9 | use squid_ewe::{ 10 | container::*, 11 | env::*, 12 | }; 13 | 14 | fn build_cmd(args: &[String]) -> Command { 15 | #[cfg(debug_assertions)] 16 | eprintln!("[EWE] {args:?}"); 17 | 18 | let mut is_cpp = false; 19 | let nostdlib = env_flag(EWE_NOSTDLIB); 20 | 21 | let prog_path = if args[0].ends_with("gcc") { 22 | env_value(RISCV_GCC, RISCV_GCC_PATH) 23 | } else if args[0].ends_with("g++") { 24 | is_cpp = true; 25 | env_value(RISCV_GPP, RISCV_GPP_PATH) 26 | } else { 27 | panic!("Invalid program name {}", args[0]) 28 | }; 29 | 30 | let mut cmd = Command::new(prog_path); 31 | 32 | cmd.arg("-wrapper"); 33 | cmd.arg(env_value(EWE_WRAPPER, EWE_WRAPPER_PATH)); 34 | 35 | if nostdlib { 36 | if is_cpp { 37 | todo!("C++ not supported yet"); 38 | } else { 39 | cmd.arg("-nostdlib"); 40 | } 41 | 42 | cmd.arg(env_value(RISCV_CRT1, RISCV_CRT1_PATH)); 43 | } 44 | 45 | for arg in args.iter().skip(1) { 46 | match arg.as_str() { 47 | "-pipe" => { 48 | continue; 49 | }, 50 | _ => { 51 | cmd.arg(arg); 52 | }, 53 | } 54 | } 55 | 56 | if nostdlib { 57 | if is_cpp { 58 | todo!(); 59 | } else { 60 | cmd.arg("-lc"); 61 | } 62 | } 63 | 64 | cmd 65 | } 66 | 67 | fn has_print_prog_name(args: &[String]) -> Option { 68 | for (i, arg) in args.iter().enumerate().skip(1) { 69 | if arg.starts_with("-print-prog-name=") { 70 | return Some(i); 71 | } 72 | } 73 | 74 | None 75 | } 76 | 77 | fn check_misc_action(args: &mut Vec) { 78 | if let Some(i) = has_print_prog_name(args) { 79 | let (_, prog) = args[i].split_once('=').unwrap(); 80 | 81 | let hooked = match prog { 82 | "ar" => { 83 | println!("{}", env_value(EWE_AR, EWE_AR_PATH)); 84 | true 85 | }, 86 | "as" => { 87 | println!("{}", env_value(EWE_AS, EWE_AS_PATH)); 88 | true 89 | }, 90 | "ld" => { 91 | println!("{}", env_value(EWE_LD, EWE_LD_PATH)); 92 | true 93 | }, 94 | _ => false, 95 | }; 96 | 97 | if hooked { 98 | args.remove(i); 99 | 100 | if args.len() <= 1 { 101 | exit(0); 102 | } 103 | } 104 | } 105 | } 106 | 107 | fn main() -> ! { 108 | let mut args = env::args().collect::>(); 109 | 110 | check_misc_action(&mut args); 111 | 112 | let status = build_cmd(&args).envs(env::vars()).status().unwrap(); 113 | 114 | if let Some(code) = status.code() { 115 | exit(code); 116 | } else { 117 | exit(-1); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /squid_ewe/src/bin/ld.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | process::{ 4 | exit, 5 | Command, 6 | }, 7 | }; 8 | 9 | use squid_ewe::{ 10 | container::*, 11 | env::*, 12 | }; 13 | 14 | fn build_cmd() -> Command { 15 | let nostdlib = env_flag(EWE_NOSTDLIB); 16 | let wrapper_path = env_value(EWE_WRAPPER, EWE_WRAPPER_PATH); 17 | 18 | let mut cmd = Command::new(wrapper_path); 19 | cmd.arg(env_value(RISCV_LD, RISCV_LD_PATH)); 20 | 21 | if nostdlib { 22 | cmd.arg("-nostdlib"); 23 | cmd.arg(env_value(RISCV_CRT1, RISCV_CRT1_PATH)); 24 | } 25 | 26 | cmd.args(env::args().skip(1)); 27 | 28 | if nostdlib { 29 | cmd.arg("-L"); 30 | cmd.arg(RISCV_LIB_PATH); 31 | cmd.arg("-lc"); 32 | } 33 | 34 | cmd 35 | } 36 | 37 | fn main() -> ! { 38 | let status = build_cmd().envs(env::vars()).status().unwrap(); 39 | 40 | if let Some(code) = status.code() { 41 | exit(code); 42 | } else { 43 | exit(-1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /squid_ewe/src/bin/wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | path::Path, 4 | process::{ 5 | exit, 6 | Command, 7 | }, 8 | }; 9 | 10 | use squid_ewe::{ 11 | container::RISCV_PREFIX, 12 | toolchain::{ 13 | ArWrapper, 14 | AsWrapper, 15 | Cc1Wrapper, 16 | LdWrapper, 17 | }, 18 | }; 19 | 20 | fn forward() -> ! { 21 | let mut args = env::args().skip(1); 22 | let status = Command::new(args.next().unwrap()).args(args).envs(env::vars()).status().unwrap(); 23 | 24 | if let Some(code) = status.code() { 25 | exit(code); 26 | } else { 27 | exit(-1); 28 | } 29 | } 30 | 31 | fn extract_program(args: &[String]) -> Option<&str> { 32 | let arg = args.first()?; 33 | let path = Path::new(arg); 34 | path.file_name().and_then(|x| x.to_str()).map(|x| { 35 | if x.starts_with(RISCV_PREFIX) { 36 | &x[RISCV_PREFIX.len() + 1..] 37 | } else { 38 | x 39 | } 40 | }) 41 | } 42 | 43 | fn usage(prog: &str) { 44 | println!("USAGE: {prog} "); 45 | println!(); 46 | println!("Use this program with: gcc -wrapper {prog}"); 47 | } 48 | 49 | fn main() { 50 | let args = env::args().skip(1).collect::>(); 51 | 52 | match extract_program(&args) { 53 | Some("cc1") => { 54 | if let Some(wrapper) = Cc1Wrapper::from_cmdline(args) { 55 | #[cfg(debug_assertions)] 56 | eprintln!("[EWE] -> {wrapper:?}"); 57 | 58 | wrapper.compile(); 59 | wrapper.postprocess(); 60 | exit(0); 61 | } 62 | }, 63 | Some("as") => { 64 | if let Some(wrapper) = AsWrapper::from_cmdline(args) { 65 | #[cfg(debug_assertions)] 66 | eprintln!("[EWE] -> {wrapper:?}"); 67 | 68 | wrapper.preprocess(); 69 | wrapper.compile(); 70 | exit(0); 71 | } 72 | }, 73 | Some("ld") | Some("collect2") => { 74 | if let Some(wrapper) = LdWrapper::from_cmdline(args) { 75 | #[cfg(debug_assertions)] 76 | eprintln!("[EWE] -> {wrapper:?}"); 77 | 78 | wrapper.link(); 79 | exit(0); 80 | } 81 | }, 82 | Some("ar") => { 83 | if let Some(wrapper) = ArWrapper::from_cmdline(args) { 84 | #[cfg(debug_assertions)] 85 | eprintln!("[EWE] -> {wrapper:?}"); 86 | 87 | wrapper.archive(); 88 | exit(0); 89 | } 90 | }, 91 | Some(_prog) => { 92 | // Ignore other subprograms 93 | #[cfg(debug_assertions)] 94 | eprintln!("[EWE] Ignoring {_prog}"); 95 | }, 96 | None => { 97 | usage(&args[0]); 98 | exit(1); 99 | }, 100 | } 101 | 102 | forward() 103 | } 104 | -------------------------------------------------------------------------------- /squid_ewe/src/container.rs: -------------------------------------------------------------------------------- 1 | pub const RISCV_AS_PATH: &str = "/riscv/bin/riscv64-unknown-linux-gnu-as"; 2 | pub const RISCV_GCC_PATH: &str = "/riscv/bin/riscv64-unknown-linux-gnu-gcc"; 3 | pub const RISCV_GPP_PATH: &str = "/riscv/bin/riscv64-unknown-linux-gnu-g++"; 4 | pub const RISCV_CRT1_PATH: &str = "/riscv/sysroot/usr/lib/crt1.o"; 5 | pub const RISCV_LD_PATH: &str = "/riscv/bin/riscv64-unknown-linux-gnu-ld"; 6 | pub const RISCV_AR_PATH: &str = "/riscv/bin/riscv64-unknown-linux-gnu-ar"; 7 | pub const RISCV_LIB_PATH: &str = "/riscv/sysroot/usr/lib"; 8 | pub const RISCV_PREFIX: &str = "riscv64-unknown-linux-gnu"; 9 | 10 | pub const EWE_WRAPPER_PATH: &str = "/ewe/wrapper"; 11 | pub const EWE_AS_PATH: &str = "/ewe/as"; 12 | pub const EWE_LD_PATH: &str = "/ewe/ld"; 13 | pub const EWE_AR_PATH: &str = "/ewe/ar"; 14 | -------------------------------------------------------------------------------- /squid_ewe/src/env.rs: -------------------------------------------------------------------------------- 1 | pub const EWE_WRAPPER: &str = "EWE_WRAPPER"; 2 | pub const EWE_AS: &str = "EWE_AS"; 3 | pub const EWE_LD: &str = "EWE_LD"; 4 | pub const EWE_AR: &str = "EWE_AR"; 5 | pub const EWE_NOSTDLIB: &str = "EWE_NOSTDLIB"; 6 | pub const RISCV_GCC: &str = "RISCV_GCC"; 7 | pub const RISCV_GPP: &str = "RISCV_G++"; 8 | pub const RISCV_CRT1: &str = "RISCV_CRT1"; 9 | pub const RISCV_AR: &str = "RISCV_AR"; 10 | pub const RISCV_AS: &str = "RISCV_AS"; 11 | pub const RISCV_LD: &str = "RISCV_LD"; 12 | 13 | pub fn env_value(key: &str, default: &str) -> String { 14 | if let Ok(value) = std::env::var(key) { 15 | value 16 | } else { 17 | default.to_string() 18 | } 19 | } 20 | 21 | pub fn env_flag(key: &str) -> bool { 22 | if let Ok(value) = std::env::var(key) { 23 | matches!(value.to_ascii_lowercase().as_str(), "1" | "yes" | "on" | "true") 24 | } else { 25 | false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /squid_ewe/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `ewe` is a compiler wrapper toolset that extracts metadata from C 2 | //! code and stores it in addition to the generated binaries in `.ewe` files. 3 | //! Its primary purpose is to tackle the information loss problem during compilation 4 | //! and reconstruct basic block boundaries in machine code. 5 | //! This enables CFG reconstruction from C code making use of the `goto*` extension. 6 | //! 7 | //! This crate is a helper for [squid](https://github.com/fkie-cad/squid) and is not meant to be 8 | //! generally usable. Use at your own risk. 9 | 10 | mod asm; 11 | mod getopt; 12 | mod listing; 13 | 14 | pub mod container; 15 | pub mod env; 16 | pub mod toolchain; 17 | pub use listing::{ 18 | Listing, 19 | ListingFunction, 20 | EXTENSION, 21 | }; 22 | -------------------------------------------------------------------------------- /squid_ewe/src/toolchain/ar.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::OpenOptions, 3 | io::Write, 4 | process::{ 5 | exit, 6 | Command, 7 | }, 8 | }; 9 | 10 | use crate::{ 11 | getopt::{ 12 | ArgList, 13 | GetoptParser, 14 | OptVal, 15 | }, 16 | listing::EXTENSION, 17 | }; 18 | 19 | fn command_as_opt(cmdline: &ArgList) -> bool { 20 | cmdline.arg_present('d') 21 | || cmdline.arg_present('m') 22 | || cmdline.arg_present('p') 23 | || cmdline.arg_present('q') 24 | || cmdline.arg_present('r') 25 | || cmdline.arg_present('s') 26 | || cmdline.arg_present('t') 27 | || cmdline.arg_present('x') 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct ArWrapper { 32 | args: Vec, 33 | inputs: Vec, 34 | output: String, 35 | } 36 | 37 | impl ArWrapper { 38 | pub fn from_cmdline(args: Vec) -> Option { 39 | let parser = GetoptParser::new() 40 | .optstring("dmpqrstxabcDfiMNoOPsSTuvV") 41 | .long("plugin", OptVal::Required, None) 42 | .long("target", OptVal::Required, None) 43 | .long("output", OptVal::Required, None) 44 | .long("record-libdeps", OptVal::Required, None) 45 | .long("thin", OptVal::None, None) 46 | .short('M', OptVal::Required, None); 47 | let cmdline = parser.parse_long(&args).unwrap(); 48 | let mut i; 49 | 50 | if cmdline.arg_present('M') { 51 | return None; 52 | } 53 | 54 | let is_create = if command_as_opt(&cmdline) { 55 | i = 0; 56 | cmdline.arg_present('q') || cmdline.arg_present('r') 57 | } else { 58 | i = 1; 59 | let command = cmdline.positionals()[0]; 60 | command.contains('q') || command.contains('r') 61 | }; 62 | 63 | if !is_create { 64 | return None; 65 | } 66 | 67 | let mut inputs = Vec::new(); 68 | let output = cmdline.positionals()[i].to_string(); 69 | 70 | i += 1; 71 | 72 | while i < cmdline.positionals().len() { 73 | inputs.push(cmdline.positionals()[i].to_string()); 74 | i += 1; 75 | } 76 | 77 | Some(Self { 78 | args, 79 | inputs, 80 | output, 81 | }) 82 | } 83 | 84 | pub fn archive(&self) { 85 | /* Run ar */ 86 | let status = Command::new(&self.args[0]).args(&self.args[1..]).envs(std::env::vars()).status().unwrap(); 87 | 88 | if let Some(code) = status.code() { 89 | if code != 0 { 90 | exit(code); 91 | } 92 | } else { 93 | exit(-1); 94 | } 95 | 96 | /* Merge metadata files */ 97 | let mut output = OpenOptions::new() 98 | .read(true) 99 | .write(true) 100 | .create(true) 101 | .append(true) 102 | .open(format!("{}.{}", self.output, EXTENSION)) 103 | .unwrap(); 104 | 105 | for input in &self.inputs { 106 | let input = OpenOptions::new().read(true).open(format!("{input}.{EXTENSION}")); 107 | 108 | if let Ok(mut input) = input { 109 | output.write_all(&[0xC]).unwrap(); 110 | std::io::copy(&mut input, &mut output).unwrap(); 111 | output.write_all(&[0xA]).unwrap(); 112 | } 113 | } 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | 121 | #[test] 122 | fn test_glibc() { 123 | let wrapper = ArWrapper::from_cmdline(vec![ 124 | "/ewe/ar".to_string(), 125 | "cr".to_string(), 126 | "librpc_compat_pic.a".to_string(), 127 | "compat-auth_des.os".to_string(), 128 | "compat-auth_unix.os".to_string(), 129 | "compat-clnt_gen.os".to_string(), 130 | ]); 131 | println!("{:?}", wrapper); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /squid_ewe/src/toolchain/gas.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::Write, 3 | process::{ 4 | exit, 5 | Command, 6 | }, 7 | }; 8 | 9 | use crate::{ 10 | asm::separate_statements, 11 | getopt::{ 12 | GetoptParser, 13 | OptVal, 14 | }, 15 | listing::EXTENSION, 16 | }; 17 | 18 | const OPTION_BLACKLIST: [&str; 1] = ["-mrelax"]; 19 | 20 | #[derive(Debug)] 21 | pub struct AsWrapper { 22 | args: Vec, 23 | inputs: Vec, 24 | output: String, 25 | } 26 | 27 | impl AsWrapper { 28 | pub fn from_cmdline(args: Vec) -> Option { 29 | let parser = GetoptParser::new() 30 | .optstring("o:JKLMRWZa::Dg::I:vwXt:") 31 | .optstring("O::g::G:") 32 | .long("alternate", OptVal::None, None) 33 | .long("compress-debug-sections", OptVal::Optional, None) 34 | .long("nocompress-debug-sections", OptVal::None, None) 35 | .long("debug-prefix-map", OptVal::Required, None) 36 | .long("defsym", OptVal::Required, None) 37 | .long("dump-config", OptVal::None, None) 38 | .long("emulation", OptVal::Required, None) 39 | .long("execstack", OptVal::None, None) 40 | .long("noexecstack", OptVal::None, None) 41 | .long("size-check", OptVal::Required, None) 42 | .long("elf-stt-common", OptVal::Required, None) 43 | .long("sectname-subst", OptVal::None, None) 44 | .long("generate-missing-build-notes", OptVal::Required, None) 45 | .long("fatal-warnings", OptVal::None, None) 46 | .long("gdwarf-cie-version", OptVal::Required, None) 47 | .long("gen-debug", OptVal::None, None) 48 | .long("gstabs", OptVal::None, None) 49 | .long("gstabs+", OptVal::None, None) 50 | .long("gdwarf-2", OptVal::None, None) 51 | .long("gdwarf-3", OptVal::None, None) 52 | .long("gdwarf-4", OptVal::None, None) 53 | .long("gdwarf-5", OptVal::None, None) 54 | .long("gdwarf-sections", OptVal::None, None) 55 | .long("hash-size", OptVal::Required, None) 56 | .long("help", OptVal::None, None) 57 | .long("itbl", OptVal::Required, None) 58 | .long("keep-locals", OptVal::None, None) 59 | .long("listing-lhs-width", OptVal::Required, None) 60 | .long("listing-lhs-width2", OptVal::Required, None) 61 | .long("listing-rhs-width", OptVal::Required, None) 62 | .long("listing-cont-lines", OptVal::Required, None) 63 | .long("MD", OptVal::Required, None) 64 | .long("mri", OptVal::None, None) 65 | .long("nocpp", OptVal::None, None) 66 | .long("no-pad-sections", OptVal::None, None) 67 | .long("no-warn", OptVal::None, None) 68 | .long("reduce-memory-overheads", OptVal::None, None) 69 | .long("statistics", OptVal::None, None) 70 | .long("strip-local-absolute", OptVal::None, None) 71 | .long("version", OptVal::None, None) 72 | .long("verbose", OptVal::None, None) 73 | .long("target-help", OptVal::None, None) 74 | .long("traditional-format", OptVal::None, None) 75 | .long("warn", OptVal::None, None) 76 | .long("multibyte-handling", OptVal::Required, None) 77 | .long("march", OptVal::Required, None) 78 | .short('f', OptVal::Required, None) 79 | .long("mabi", OptVal::Required, None) 80 | .long("misa-spec", OptVal::Required, None) 81 | .long("mpriv-spec", OptVal::Required, None) 82 | .short('m', OptVal::Required, None); 83 | 84 | let cmdline = parser.parse_long_only(&args).unwrap(); 85 | let output = if let Some(output) = cmdline.arg_value('o') { 86 | output.to_string() 87 | } else { 88 | return None; 89 | }; 90 | let mut inputs = Vec::new(); 91 | 92 | for input in cmdline.positionals() { 93 | if *input != "-" { 94 | inputs.push(input.to_string()); 95 | } 96 | } 97 | 98 | Some(Self { 99 | args, 100 | inputs, 101 | output, 102 | }) 103 | } 104 | 105 | pub fn preprocess(&self) { 106 | for filename in &self.inputs { 107 | let input = std::fs::read(filename).unwrap(); 108 | let output = separate_statements(&input, filename); 109 | let mut file = std::fs::OpenOptions::new().create(true).truncate(true).write(true).open(filename).unwrap(); 110 | file.write_all(&output).unwrap(); 111 | } 112 | } 113 | 114 | pub fn compile(&self) { 115 | let listing = format!("{}.{}", self.output, EXTENSION); 116 | let status = Command::new(&self.args[0]) 117 | .arg(format!("-almd={listing}")) 118 | .args(self.args[1..].iter().filter(|x| !OPTION_BLACKLIST.contains(&x.as_str()))) 119 | .arg("-mno-relax") 120 | .envs(std::env::vars()) 121 | .status() 122 | .unwrap(); 123 | 124 | if let Some(code) = status.code() { 125 | if code != 0 { 126 | exit(code); 127 | } 128 | } else { 129 | exit(-1); 130 | } 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::*; 137 | 138 | #[test] 139 | fn test_assembly_preprocessing() { 140 | let output = separate_statements( 141 | b".file \":;\\\"\\\\\"# single-line comment\n/* multi-line\ncomment */.L0:.directive;inst;\n", 142 | "", 143 | ); 144 | println!("{}", std::str::from_utf8(&output).unwrap()); 145 | } 146 | 147 | #[test] 148 | fn test_as_wrapper1() { 149 | let wrapper = AsWrapper::from_cmdline(vec!["as".to_string(), "--help".to_string()]); 150 | assert!(wrapper.is_none()); 151 | } 152 | 153 | #[test] 154 | fn test_as_wrapper2() { 155 | let wrapper = AsWrapper::from_cmdline(vec![ 156 | "as".to_string(), 157 | "-fpic".to_string(), 158 | "--traditional-format".to_string(), 159 | "-march=rv64imafd".to_string(), 160 | "-mabi=lp64d".to_string(), 161 | "-misa-spec=2.2".to_string(), 162 | "-o".to_string(), 163 | "/tmp/cc5hz51Q.o".to_string(), 164 | "/tmp/ccNyPyiT.s".to_string(), 165 | "-".to_string(), 166 | ]) 167 | .unwrap(); 168 | assert_eq!(wrapper.inputs, &["/tmp/ccNyPyiT.s"]); 169 | assert_eq!(wrapper.output, "/tmp/cc5hz51Q.o"); 170 | } 171 | 172 | #[test] 173 | fn test_glibc() { 174 | let wrapper = AsWrapper::from_cmdline(vec![ 175 | "as".to_string(), 176 | "-I".to_string(), 177 | "../include".to_string(), 178 | "--gdwarf-5".to_string(), 179 | "--traditional-format".to_string(), 180 | "-fpic".to_string(), 181 | "-march=rv64imafd".to_string(), 182 | "-march=rv64imafd".to_string(), 183 | "-mabi=lp64d".to_string(), 184 | "-misa-spec=2.2".to_string(), 185 | "-o".to_string(), 186 | "/io/test-data/glibc/build/libio/clearerr.os".to_string(), 187 | "/tmp/ccJfUCIc.s".to_string(), 188 | ]) 189 | .unwrap(); 190 | println!("{:?}", wrapper); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /squid_ewe/src/toolchain/mod.rs: -------------------------------------------------------------------------------- 1 | mod ar; 2 | mod cc1; 3 | mod gas; 4 | mod ld; 5 | 6 | pub use ar::ArWrapper; 7 | pub use cc1::Cc1Wrapper; 8 | pub use gas::AsWrapper; 9 | pub use ld::LdWrapper; 10 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squid-tests" 3 | edition.workspace = true 4 | publish = false 5 | 6 | [dev-dependencies] 7 | squid_ewe = { path = "../squid_ewe" } 8 | squid = { path = "../squid" } 9 | libafl = "0.13" 10 | libafl_bolts = "0.13" 11 | serde = "1.0" 12 | mimalloc = { version = "0.1", default-features = false } 13 | 14 | [[test]] 15 | name = "benchmark" 16 | path = "benchmark/benchmark.rs" 17 | -------------------------------------------------------------------------------- /tests/benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | bench 2 | emu.* 3 | -------------------------------------------------------------------------------- /tests/benchmark/Makefile: -------------------------------------------------------------------------------- 1 | CC=/riscv/bin/riscv64-unknown-linux-gnu-gcc 2 | CFLAGS=-Wall -Wextra -Wpedantic -Werror -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= -nostdlib -Wl,-emain -Wl,-dynamic-linker,/riscv/sysroot/lib/ld-2.33.so 3 | 4 | bench: main.c 5 | $(CC) -o $@ $(CFLAGS) $^ 6 | 7 | .PHONY: clean 8 | clean: 9 | @rm -fv bench 10 | -------------------------------------------------------------------------------- /tests/benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking squid 2 | 3 | ## Results 4 | QEMU: runtime 0m30.763s => 1,977,474,525 instrs/s 5 | squid: runtime 39.14s => 652,456,705.0020577 instr/s 6 | -------------------------------------------------------------------------------- /tests/benchmark/benchmark.rs: -------------------------------------------------------------------------------- 1 | use squid::{ 2 | backends::clang::ClangBackend, 3 | event::EVENT_BREAKPOINT, 4 | runtime::Runtime, 5 | Compiler, 6 | }; 7 | 8 | #[test] 9 | fn benchmark_emulator() { 10 | let compiler = Compiler::loader() 11 | .binary("../tests/benchmark/bench") 12 | .load() 13 | .unwrap(); 14 | let backend = ClangBackend::builder() 15 | .stack_size(1024 * 1024) 16 | .progname("bench") 17 | .source_file("../tests/benchmark/emu.c") 18 | .cflag("-Ofast") 19 | .cflag("-ffast-math") 20 | .cflag("-flto") 21 | .cflag("-s") 22 | .cflag("-fno-stack-protector") 23 | .cflag("-march=native") 24 | .cflag("-fomit-frame-pointer") 25 | .cflag("-g") 26 | .build_symbol_table(false) 27 | .update_pc(false) 28 | .update_last_instruction(false) 29 | .enable_uninit_stack(false) 30 | .build() 31 | .unwrap(); 32 | let mut runtime = compiler.compile(backend).unwrap(); 33 | 34 | let start = std::time::Instant::now(); 35 | match runtime.run() { 36 | Ok(EVENT_BREAKPOINT) => {}, 37 | e => unreachable!("{:?}", e), 38 | } 39 | let secs = start.elapsed().as_secs_f64(); 40 | 41 | println!("{} instr/s", runtime.get_executed_instructions() as f64 / secs); 42 | } 43 | -------------------------------------------------------------------------------- /tests/benchmark/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define MEM_SIZE (16 * 1024 * 1024) 4 | char data[MEM_SIZE]; 5 | 6 | uint64_t state = 0x12345; 7 | static inline __attribute__((always_inline)) uint64_t rand (void) { 8 | uint64_t x = state; 9 | x ^= x << 13; 10 | x ^= x >> 7; 11 | x ^= x << 17; 12 | return state = x; 13 | } 14 | 15 | int main (void) { 16 | for (unsigned long i = 0; i < (1UL << 28); ++i) { 17 | uint64_t index = rand() % (MEM_SIZE - 8); 18 | 19 | switch (rand() % 4) { 20 | case 0: { 21 | uint8_t* p = (uint8_t*) &data[index]; 22 | *p = (uint8_t) rand(); 23 | break; 24 | } 25 | 26 | case 1: { 27 | uint16_t* p = (uint16_t*) &data[index]; 28 | *p = (uint16_t) rand(); 29 | break; 30 | } 31 | 32 | case 2: { 33 | uint32_t* p = (uint32_t*) &data[index]; 34 | *p = (uint32_t) rand(); 35 | break; 36 | } 37 | 38 | case 3: { 39 | uint64_t* p = (uint64_t*) &data[index]; 40 | *p = (uint64_t) rand(); 41 | break; 42 | } 43 | 44 | default: { 45 | __builtin_unreachable(); 46 | } 47 | } 48 | } 49 | 50 | __builtin_trap(); 51 | } 52 | -------------------------------------------------------------------------------- /wiki/EVENTS.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Events are a means of communication between the guest and the host and 4 | can be used to pass information to the harness and back. 5 | They can be thrown at any point in the code with the `FireEvent` IR instruction. 6 | Two events are built into `squid`. The syscall event that is caused by an ECALL instruction 7 | and the breakpoint event that is caused by an EBREAK instruction. 8 | You can also define custom events. 9 | 10 | ### Creating events 11 | Events can be created inside passes with the `EventPool`. 12 | Call `EventPool::add_event()` with the name of your custom event to get a unique event ID in return. 13 | This event ID can be used in the `FireEvent` instruction to throw your custom event. 14 | 15 | ### Handling events 16 | An event ID is the return value of the `Runtime::run` method that can be used to execute a target. 17 | The host must inspect that return value and handle it accordingly. 18 | 19 | ### Event channel 20 | Events have their own arguments and return values such that you can communicate more than just an event ID. 21 | The arguments and return values are placed into the "event channel", which is a buffer of a fixed size. 22 | Push data into the event channel before throwing an event, by using the `PushEventArgs` IR instruction. 23 | Then, when harness handles the event, it can access the arguments via the `Runtime::event_channel()` method. 24 | The harness can in turn place return values into the event channel after handling an event. This is done with 25 | the `Runtime::event_channel_mut()` method. The return values can be collected inside the guest with the `CollectEventReturns` 26 | IR instruction. 27 | 28 | ## Example 29 | The following example shows how to hook the `malloc()` function of the libc by throwing a custom `HANDLE_MALLOC` event 30 | and using the return value of the event as the return value of the function. 31 | 32 | ```rs 33 | use squid::*; 34 | 35 | struct MallocPass; 36 | 37 | impl Pass for MallocPass { 38 | fn run(&mut self, image: &mut ProcessImage, event_pool: &mut EventPool, logger: &Logger) -> Result<(), ()> { 39 | // Create a new event in the event pool 40 | let event_id = event_pool.add_event("HANDLE_MALLOC"); 41 | 42 | // Search the malloc function 43 | let libc = image.elf_by_filename_mut("libc.so.6"); 44 | for section in libc.iter_sections_mut() { 45 | for symbol in section.iter_symbols_mut() { 46 | if symbol.name("malloc").is_some() { 47 | 48 | // Get the CFG of the function 49 | let function = symbol.chunk_mut(1).content_mut(); 50 | let cfg = function.cfg_mut(); 51 | 52 | // This bb pushes the argument onto the event channel and throws the HANDLE_MALLOC event 53 | let mut bb1 = BasicBlock::new(); 54 | let a0 = bb1.load_gp_register(GpRegister::a0); 55 | bb1.push_event_args([a0]); 56 | bb1.fire_event(event_id); 57 | 58 | // This bb collects the address from the harness and returns it 59 | let mut bb2 = BasicBlock::new(); 60 | let rets = bb2.collect_event_returns(1); 61 | bb2.store_gp_register(GpRegister::a0, rets[0]); 62 | let ra = bb2.load_gp_register(GpRegister::ra); 63 | bb2.jump(ra); 64 | 65 | // Replace the malloc function with these two basic blocks 66 | cfg.clear(); 67 | let bb2_id = cfg.add_basic_block(bb2); 68 | bb1.add_edge(Edge::Next(bb2_id)); 69 | let bb1_id = cfg.add_basic_block(bb1); 70 | cfg.set_entry(bb1_id); 71 | 72 | // Now the malloc() function consists entirely of the two basic blocks from above and 73 | // the harness has full control over the allocation strategy. 74 | } 75 | } 76 | } 77 | 78 | Ok(()) 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /wiki/PROCESS_IMAGE/README.md: -------------------------------------------------------------------------------- 1 | # Process Image 2 | 3 | The process image is a data structure that holds the code and data of ELF files. 4 | It is the result of a dynamic linking process where all dependencies are collected, 5 | allocatable sections are parsed, symbols are resolved, etc. 6 | The only difference to traditional dynamic linking is that a process image is a hierachical tree structure and not a linear memory image. 7 | This hierachical approach enables us to modify the ELF files' content without messing up its overall structure. 8 | Its primary purpose is to make manipulations of global variables and functions easy. 9 | 10 | Creating a process image is the first step in using `squid` and can be done with `Compiler::loader`: 11 | ```rs 12 | let mut compiler = Compiler::loader() 13 | // The binary that we want to emulate 14 | .binary("/path/to/binary") 15 | // Directories that contain the dependencies of the binary similar to LD_LIBRARY_PATH 16 | .search_path("/path/with/deps") 17 | // List of shared objects to preload similar to LD_PRELOAD 18 | .preload("/path/to/library.so") 19 | // Start the ELF-loading process 20 | .load() 21 | .expect("Loading binary failed"); 22 | ``` 23 | 24 | This produces a process image that looks something like this (excerpt, the full graph can be found [here](./symimg.svg)): 25 | ![](./symimg.png) 26 | 27 | As you can see the process image is a tree. 28 | The root points to the loaded ELF files - in this case a "helloworld" executable and its dependency "libc.so.6". 29 | The children of ELF files are their allocatable sections (identified here by their permission bits "rwx"). 30 | The children of sections are all ELF symbols that are inside those sections. 31 | For example, the symbol "main" is a child of the section "r-x" because the program has a `main()` function. 32 | The leafs of the process image are so-called "chunks" that hold the actual contents of a symbol. 33 | They tell us whether to interpret the stream of bytes as either code, data or pointers. 34 | 35 | One of the things you're gonna do most frequently with a process image is traversing it. 36 | This can be done like so: 37 | ```rs 38 | for elf in compiler.process_image().iter_elfs() { 39 | for section in elf.iter_sections() { 40 | for symbol in section.iter_symbols() { 41 | for chunk in symbol.iter_chunks() { 42 | match chunk.content() { 43 | ChunkContent::Code(code) => { 44 | // ... 45 | }, 46 | ChunkContent::Data { content, perms } => { 47 | // ... 48 | }, 49 | ChunkContent::Pointer(pointer) => { 50 | // ... 51 | }, 52 | } 53 | } 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | You can also add new nodes, delete nodes or modify existing nodes. 60 | To add new nodes call their respective builder objects, e.g. `Elf::builder()` or 61 | `Section::builder()` and to insert the newly created nodes use the `insert_*` 62 | methods like `elf.insert_section(...)` or `section.insert_symbol(...)`. 63 | The following example demonstrates how to create an AFL-style coverage map and insert 64 | it into the process image. 65 | 66 | ```rs 67 | // This function will make it appear as if the AFL coverage map has been statically linked 68 | // into the application. It creates a new ELF file with a single symbol "coverage_map" that 69 | // holds the entire coverage map. 70 | // This way, we can access the map from the harness and from inside the guest. 71 | fn build_coverage_map(image: &mut ProcessImage, map_size: usize) { 72 | // The map will be readable and writable 73 | let mut perms = Perms::default(); 74 | perms.make_readable(); 75 | perms.make_writable(); 76 | 77 | // The chunk holds the actual data and permission bits 78 | let chunk = Chunk::builder() 79 | .uninitialized_data(map_size, perms) 80 | .build() 81 | .unwrap(); 82 | 83 | // Create a symbol named "coverage_map" that contains the chunk 84 | let mut symbol = Symbol::builder() 85 | .public_name("coverage_map") 86 | .size(map_size) 87 | .build() 88 | .unwrap(); 89 | 90 | // Create a section that can hold the symbol 91 | let mut section = Section::builder() 92 | .perms(perms) 93 | .size(map_size) 94 | .build() 95 | .unwrap(); 96 | 97 | // Finally, create the ELF file that contains the section 98 | let mut elf = Elf::builder() 99 | .path("") 100 | .build() 101 | .unwrap(); 102 | 103 | // Chain all objects together 104 | symbol.insert_chunk(chunk); 105 | section.insert_symbol(symbol); 106 | elf.insert_section(section); 107 | image.insert_elf(elf); 108 | } 109 | ``` 110 | 111 | ## Symbolization 112 | One key thing that has been left out so far is how `squid` handles code. 113 | Since the point of the process image is to manipulate binaries, we run into one particular problem. 114 | Whenever there are pointers to objects inside ELF files, they may become invalid when manipulating the ELF files' content. 115 | The way `squid` solves this problem is that it abstracts pointers with concrete values away to higher-level 116 | "symbolic pointers". 117 | A symbolic pointer also is a pointer but it points to a chunk in the process image instead of a memory location. 118 | You may have noticed that in the image above each node in the tree is prefixed with a number in square brackets. 119 | This is the ID of the element. Each child of each node gets a unique ID such that we can identify chunks with a 120 | 5-tuple of IDs: `(elf_id, section_id, symbol_id, chunk_id)`. 121 | For example, every concrete pointer to the `main()` function has the value `0x4c0` in the ELF file but gets replaced with `(1, 2, 5, 1)` 122 | in the process image. 123 | 124 | In order to do this symbolization technique, the RISC-V code has to be lifted into an IR that is specifically designed 125 | to make symbolization easy. This also means that when you want to modify functions, you have to 126 | do it on the higher-level IR instead of the raw RISC-V code. 127 | -------------------------------------------------------------------------------- /wiki/PROCESS_IMAGE/symimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/squid/a081a984f2d54362498f332496c93113858db514/wiki/PROCESS_IMAGE/symimg.png -------------------------------------------------------------------------------- /wiki/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This page briefly introduces the most important aspects of `squid`. 4 | Since `squid` is a RISC-V emulator with AOT-compilation its usage can be divided into 3 phases: 5 | 6 | 1. Compiling the target to RISC-V 7 | 2. Doing the AOT compilation 8 | 1. Loading the binary and all its dependencies 9 | 2. Running passes to modify code or data 10 | 3. Compiling the code to the native ISA 11 | 3. Emulating the target by running the compiled code 12 | 13 | Each phase is explained in more detail below. 14 | 15 | ## Compiling the target 16 | Follow the instructions in [TOOLCHAIN](./TOOLCHAIN.md) to compile your target to RISC-V. 17 | Please note, that you also need to compile all of the targets dependencies to RISC-V. 18 | 19 | ## Loading the binary 20 | Once you have compiled the binary and all it's dependencies to RISC-V, the next step is to create the so-called "process image". 21 | The process image is the result of ELF-loading the fuzz target and lifting all functions into an IR. 22 | All dependencies of the program are collected, symbol imports are being resolved and all pointers are "symbolized" in 23 | a manner similar to [RetroWrite](https://github.com/HexHive/RetroWrite). 24 | This creates an in-memory data structure that makes all functions and global variables of the loaded ELF files available for 25 | inspection and modification. Because of the symbolization we can freely modify everything without having to worry about 26 | invalidating references or offsets. 27 | 28 | Load your binary like so: 29 | ```rs 30 | let mut compiler = Compiler::loader() 31 | // The binary that we want to emulate 32 | .binary("/path/to/binary") 33 | // Directories that contain the dependencies of the binary similar to LD_LIBRARY_PATH 34 | .search_path("/path/with/deps") 35 | // List of shared objects to preload similar to LD_PRELOAD 36 | .preload("/path/to/library.so") 37 | // Start the ELF-loading process 38 | .load() 39 | .expect("Loading binary failed"); 40 | ``` 41 | 42 | For more information about the process image, see [PROCESS\_IMAGE](./PROCESS_IMAGE/). 43 | 44 | ## Running Passes 45 | Once the process image has been created, we can run passes to modify functions or data. 46 | A pass in `squid` is anything that implements the `Pass` trait. Otherwise, it functions exactly 47 | like an LLVM pass. 48 | 49 | ```rs 50 | struct MyPass; 51 | 52 | impl Pass for MyPass { 53 | fn name(&self) -> String { 54 | // return the name of this pass here 55 | } 56 | 57 | fn run(&mut self, image: &mut ProcessImage, event_pool: &mut EventPool, logger: &Logger) -> Result<(), String> { 58 | // modify functions or data in the process image here 59 | } 60 | } 61 | 62 | // Run the pass with the compiler 63 | compiler.run_pass(&mut MyPass {}).expect("Pass had an error"); 64 | ``` 65 | 66 | ## Creating a Runtime 67 | The final step before emulation is to AOT-compile all functions to the host ISA. 68 | This is the responsibility of a "backend". 69 | The backend receives a process image and produces a "runtime" that interfaces with the target program. 70 | In a similar manner like before, a backend is anything that implements the `Backend` trait and a runtime 71 | is anything that implements the `Runtime` trait. 72 | 73 | Currently, `squid` comes with the `ClangBackend` and the `ClangRuntime`: 74 | ```rs 75 | // Create the backend responsible for compilation 76 | let backend = ClangBackend::builder() 77 | .stack_size(1 * 1024 * 1024) // Size of the stack region 78 | .progname("my-fuzz-target") // argv[0] 79 | .arg("--with-bugs-pls") // argv[1] 80 | .build() 81 | .expect("Could not configure backend"); 82 | 83 | // Start AOT-compilation with the given backend and get a runtime in return 84 | let runtime = compiler.compile(backend).expect("Backend had an error"); 85 | ``` 86 | 87 | ## Running the target 88 | Once we have obtained the runtime, we can start interacting with the program. 89 | The runtime gives us access to registers, memory, lets us create/restore snapshots, 90 | and of course run our target with the `Runtime::run` method. 91 | Running the target will throw certain events like system calls that 92 | must be handled by the harness. 93 | It is also possible to throw custom events that can be created inside passes. 94 | For more on that see [EVENTS](./EVENTS.md). 95 | 96 | Run the target like so: 97 | ```rs 98 | loop { 99 | match runtime.run() { 100 | Ok(event) => match event { 101 | EVENT_SYSCALL => { 102 | // Handle syscall 103 | }, 104 | CUSTOM_EVENT => { 105 | // Handle custom events from passes 106 | }, 107 | }, 108 | Err(fault) => { 109 | // a fault (e.g. segfault) happened 110 | break; 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | -------------------------------------------------------------------------------- /wiki/TOOLCHAIN.md: -------------------------------------------------------------------------------- 1 | # Toolchain 2 | 3 | `squid` is a RISC-V emulator, so you have to compile every target you want to fuzz to RISC-V. 4 | This can be done in the docker container that comes with `squid` and provides you with a RISC-V gcc toolchain. 5 | 6 | You can either build the toolchain from scratch: 7 | ``` 8 | docker pull rust:latest 9 | docker pull archlinux:latest 10 | docker build -t squid-toolchain --build-arg="jobs=$(nproc)" . 11 | ``` 12 | 13 | Or you can download a pre-built image from docker hub (900MB): 14 | ``` 15 | docker pull pdfkie/squid-toolchain:1.0.0 16 | ``` 17 | 18 | Inside the container, you can find the cross-compiler at `/riscv/bin/riscv64-unknown-linux-gnu-gcc` and the sysroot at 19 | `/riscv/sysroot/`. 20 | 21 | Please note that `squid` has certain requirements for the binaries it can emulate. 22 | The binaries __must__ be compiled with the flags 23 | ``` 24 | -fPIE -pie -O0 -g -fno-jump-tables -mno-relax -D__thread= 25 | ``` 26 | and must not use thread-local storage, otherwise they won't work with `squid`. 27 | 28 | If your target makes use of the C extension that enables `goto*` statements and references to labels, 29 | use `/ewe/gcc` instead of `riscv64-unknown-linux-gnu-gcc`. 30 | This tool is a compiler wrapper that collects certain metadata about the code and is necessary to 31 | correctly reconstruct CFG's. It produces an `.ewe` file alongside your binary that contains said metadata. 32 | For more information on this see the documentation of [ewe](../ewe). 33 | 34 | --------------------------------------------------------------------------------