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