Skip to content(if available)orjump to list(if available)

A more robust raw OpenBSD syscall demo

fwsgonzo

The inline assembly is not idiomatic. Today you should be using register asm. Here is a RISC-V example:

    register long a0 asm("a0") = arg0;
    register long syscall_id asm("a7") = n;

    asm volatile ("ecall" : "+r"(a0) : "r"(syscall_id));

    return a0;
This is an example where a0 is an in/out integer. For memory, change long a0 to a pointer to some struct and add a "m" input or "+m" in/out. It's even easier in other languages like Rust and Zig.

LegionMammal978

> The inline assembly is not idiomatic. Today you should be using register asm.

Who's writing these idioms? I've always seen register variables as an alternative, not the default option, except for those registers which have no constraint code.

saagarjha

I’d really prefer using inline assembly tbh

sim7c00

you can also out register preferences directly into the asm volatile line.

asm ( assembler template : output operands (optional) : input operands (optional) : clobbered registers list (optional) );

clobbered register list will give same hints to compiler... register keyword is also a hint not a fixed thing so i cant imagine its really handled differently?

might be wrong, but it seem earier to me to use all the features of the asm call itself rather than outside of it pre-specifying these preferences.

pjmlp

Actually nowadays ideally we would already caught up with ESPOL/NEWP from 1961 and use only intrisics.

Apparently that is a VC++ only thing.

debatem1

Seems like an interesting if maybe not practical protection to implement in eBPF for programs that never make a naked syscall.

Step one would be to ensure that every syscall has a wrapper. Place a uprobe at the start of that wrapper which, when hit, sets a per-thread permission bit and a per-thread-per-syscall permission bit in an eBPF map. Place a corresponding uretprobe that clears the per-thread-per-syscall bit. For each syscall place a kprobe which checks the per-thread table to make sure the thread is one which has enabled the feature, and which then checks to make sure the per-thread-per-syscall bit is set for that syscall. If not, sigkill.

Performance would probably suck but it seems like it would protect the syscall entrypoints enough to do some potentially interesting attack surface reduction. The question is really why you would do that there instead of by attaching to, say, the LSM hooks where you have stronger guarantees vis a vis userspace.

saagarjha

What’s the threat model this protects against?

oguz-ismail

Why involve C at all? This is much cleaner in assembly

            .global _start
            .data
    what:   .string "hello\n"
            .set    len, .-what - 1
            .text
    _start:
            mov     w0, 1
            adr     x1, what
            mov     w2, len
            mov     w8, 4
    99:     svc     0
            dsb     nsh
            isb
            mov     w8, 1
    98:     svc     0
            .section        .openbsd.syscalls, ""
            .long   99b, 4
            .long   98b, 1
            .section        .note.openbsd.ident, "a"
            .long   8, 4, 1
            .string "OpenBSD"
            .long   0

johnisgood

Isn't "dsb nsh" or "isb" redundant for simple syscalls like write and exit?

oguz-ismail

Without them it'll segfault on QEMU, IIRC OpenBSD libc uses them invariably as well. I don't know how it fares on real hardware

pjmlp

Probably due to the audience.

I can grasp the example, but back in my days, if we wanting something faster than interpreted BASIC, Assembly was the way.

Folks nowadays start with Python and JavaScript.

hello_computer

especially when confronted with a soup of C flags and declarations, and still not being entirely sure what the C compiler is going to emit

rollcat

I like how Go provides the "syscall" package in the standard library. It's OS/ARCH specific, and takes every precaution to call the raw thing safely - notably syscall.ForkExec on UNIX platforms (digging thru the source made me really appreciate the scope of their work).

It "feels" very low-level in an otherwise very high-level language, but provides enough power to implement an entire userland from initrd up, libc- and asm-free (check out Gokrazy and u-root).

OpenBSD and Go have always been at odds here. Go really wants to produce static executables whenever possible, and do syscalls directly; OpenBSD really wants to prevent user programs from accidentally creating gadgets. I guess they've settled on dynamically linking ld.so?

actionfromafar

And the MacOS API is similar.

rollcat

It's a bit more complicated. Ideally you'd only need libdyld.dylib and libSystem.B.dylib or so, but...

    $ echo 'package main; func main() {}' > nop.go
    $ go build ./nop.go
    $ DYLD_PRINT_LIBRARIES=1 ./nop 2>&1 | wc -l
          44
OpenBSD & macOS philosophies are often surprisingly aligned in certain ways, but OpenBSD is simple, macOS is comprehensive (and - TBF quite bloated).

INTPenis

I'm sorry but I got stuck on the first sentence "Ted Unangst published dude, where are your syscalls? on flak yesterday" and as a long time fediverse operator I got insanely curious about "flak".

So I ended up on the flak tag of this blog[1], but I still can't figure out what it is. I can find no links to any source code, or any service description. Even though the blogger mentions flak being their "signature service".

I'm guessing it's a blogging platform, with ActivityPub support, but I can't find any info about how it's used.

1. https://flak.tedunangst.com/t/flak

dontdoxxme

The post you're looking for is about how they also NIH'd a source code browser: https://flak.tedunangst.com/post/humungus

mrweasel

Flak is a blogging platform... I think. He also has Honk, which is a ActivityPub server?

sim7c00

maybe to make it more concise binary you can do something like nostdinc nostdlib or freestanding and add own linker file to be more explicit about how binary is build and what to /discard/. also max page size and other linker flags can impact. its sometimes a bit trial and error to get it to spit out a small binary with only the code u really wrote.

own linker file and manual linking step imo is key. (i use gcc/ld). if u let gcc spit linker file for modern platform, u can see its full of clutter... most of it unneeded. u can also strip that one down, but i am sure u know what elf sections to put and omit, and you found all the bsd specific ones.

in the linker step u can also add symbols / calculate offsets.

in gcc u can also in the c code use attribute section('bla'). not sure if its handy in this case but maybe it'll ease somewhat these things or bring it back more into C :).

cool example :) remebering struggle tirleesly tryin to find out how to run a raw syscall on openbsd. a lot of man pages, readelfs and headaches i was so happy to get my exit code hahah