google / syzkaller

syzkaller is an unsupervised coverage-guided kernel fuzzer
Apache License 2.0
5.38k stars 1.23k forks source link

all: Add support for UEFI firmware #3132

Open heatd opened 2 years ago

heatd commented 2 years ago

Hi, I was thinking about adding UEFI support to syzkaller as a way to effectively fuzz UEFI firmware (in this case, Tianocore), which has an API which is similar-ish to regular system calls, but done through what effectively are function pointer tables (EFI protocols) that you get from other functions, up to the "root" protocol so to speak (this is the Boot and Runtime protocols, which are passed to the application on entry, like an argument).

Does this sound wanted and feasible? From what I can gather, there isn't support for function pointers in structs, nor fuzzing of functions passed in structs, as they're not part of the typical system call API. That would probably be a good start.

I think adding support for UEFI would probably help a lot making firmware more safe, as currently there's no good way to find issues apart from unit tests.

dvyukov commented 2 years ago

Hi @heatd,

Does this sound wanted and feasible?

Per se it should be feasible provided you build syzkaller as UEFI application.

From what I can gather, there isn't support for function pointers

It depends on what you want to do with function pointers. If just need to pass them around from one call to another, then this is perfectly supported by means of "resources". If you need to call these function pointers that this can be done in 2 ways:

  1. syzkaller can call map syscalls to function pointers (call at arbitrary function instead of a syscall). This is used for e.g. Windows where kernel syscall interface is user-space library functions. So you could statically populate syzkaller syscall table with addresses of all UEFI functions (see generated executor/syscalls.h which contains functions pointers for some OSes).
  2. you could add a syzkaller pseudo syscall, e.g. syz_call, which will call passed in function pointer with passed in arguments. Along the lines of:
    long syz_call(long fn, long arg0, long arg1, long arg2, long arg3) {
    return ((long(*)(long, long, long, long))fn)(arg0, arg1, arg2, arg3);
    }

Then this generic pseudo syscall can be used in syzkaller descriptions to call function pointers obtained from other syscalls:

resource uefi_fn[intptr]
get_root_fn() uefi_fn
syz_call(fn uefi_fn, ... args ...)
dvyukov commented 2 years ago

I don't know how exactly you want to do this. But if you want to build the guest part of syzkaller as UEFI application, it may be hard because currently syzkaller also runs a beefy syz-fuzzer Go binary on the target. There is #1541 that implies leaving only a thin C++ binary on the target. I suspect it may help with UEFI fuzzing as well. But it's a large task and it's not planned at the moment.

heatd commented 2 years ago

I don't know how exactly you want to do this. But if you want to build the guest part of syzkaller as UEFI application, it may be hard because currently syzkaller also runs a beefy syz-fuzzer Go binary on the target. There is #1541 that implies leaving only a thin C++ binary on the target. I suspect it may help with UEFI fuzzing as well. But it's a large task and it's not planned at the moment.

My idea was to use something like Fuchsia's HostFuzzer and only run C++ on UEFI, as that's much more doable than porting the Go runtime just for this specific task (UEFI firmware is written entirely/almost entirely in C and that's unlikely to change in the future). Actually, one more question: How does syzkaller pass stuff to the VM? Does it just use standard TCP?

dvyukov commented 2 years ago

My idea was to use something like Fuchsia's HostFuzzer

Yes, this may work.

Actually, one more question: How does syzkaller pass stuff to the VM? Does it just use standard TCP?

It depends on "stuff". syz-manager copies binaries to the target and runs them. The interface for this is generic: https://github.com/google/syzkaller/blob/master/vm/vmimpl/vmimpl.go#L37-L62 But then most implementation use ssh/scp for this.

The syz-fuzzer running on the targets also connects back to host using TCP. But that is not relevant for HostFuzzer most.

The HostFuzzer hack exists only in qemu VM implementation and it wraps syz-executors into ssh: https://github.com/google/syzkaller/blob/master/vm/qemu/qemu.go#L606-L615 so that syz-fuzzer thinks it starts syz-executor locally, but actually it's started on the target.