fwsgonzo 6 hours ago

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 an hour ago

    > 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 an hour ago

    I’d really prefer using inline assembly tbh

  • sim7c00 3 hours ago

    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 2 hours ago

    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 7 hours ago

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 an hour ago

    What’s the threat model this protects against?

oguz-ismail 6 hours ago

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 an hour ago

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

    • oguz-ismail an hour ago

      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 5 hours ago

    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 6 hours ago

    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 6 hours ago

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 5 hours ago

    And the MacOS API is similar.

    • rollcat 4 hours ago

      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 7 hours ago

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

sim7c00 3 hours ago

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