[HN Gopher] A more robust raw OpenBSD syscall demo
___________________________________________________________________
A more robust raw OpenBSD syscall demo
Author : signa11
Score : 60 points
Date : 2025-03-12 06:11 UTC (16 hours ago)
(HTM) web link (nullprogram.com)
(TXT) w3m dump (nullprogram.com)
| INTPenis wrote:
| 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 wrote:
| The post you're looking for is about how they also NIH'd a
| source code browser: https://flak.tedunangst.com/post/humungus
| mrweasel wrote:
| Flak is a blogging platform... I think. He also has Honk, which
| is a ActivityPub server?
| debatem1 wrote:
| 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 wrote:
| What's the threat model this protects against?
| debatem1 wrote:
| "Attacker has ROP and shouldn't be able to make arbitrary
| syscalls".
|
| Seems mildly useful if you have a really flexible syscall you
| can't forbid (ioctl, say) but which you only use for a
| specific narrow purpose.
| saagarjha wrote:
| If they can ROP they can jump to a syscall instruction with
| controlled arguments
| wahern wrote:
| The point of pinsyscall is that they have to jump to the
| _single_ entry point for that syscall, rather than any of
| the ~200+ syscall instructions littering the address
| space. ALSR makes finding an entry point difficult, but
| that 's easier if you only need to find any syscall
| instruction, rather than the specific one for the syscall
| you're invoking. The rationale is explained here: https:/
| /undeadly.org/cgi?action=article;sid=20230222064027
| debatem1 wrote:
| The point of what I spelled out above is that they can
| jump to the instruction but the kernel will kill the
| program if they don't go through the function up to that
| point. That allows you to restrict the arguments to the
| syscall at the point of call.
| oguz-ismail wrote:
| 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
| hello_computer wrote:
| 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
| kccqzy wrote:
| How is that an issue? You inspect the generated assembler.
| Godbolt is the industry standard tool for that, but of course
| just use objdump on the command line if a browser UI isn't
| your fancy.
|
| Mixing C is helpful because most code doesn't need to be
| written in raw assembly.
| hello_computer wrote:
| keeping assembly in asm files, c in c files, compiled by
| their respective compilers, then linked, has fewer footguns
| than inline asm
| kccqzy wrote:
| Yeah but then you have to maintain function interfaces
| between them in order to link them. The case in this
| article is for inserting one single asm instruction in an
| otherwise C codebase.
| pjmlp wrote:
| 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.
| johnisgood wrote:
| Isn't "dsb nsh" or "isb" redundant for simple syscalls like
| write and exit?
| oguz-ismail wrote:
| 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
| johnisgood wrote:
| Yeah, probably (although I am not sure) because QEMU
| doesn't fully emulate hardware behavior, but on real
| hardware some CPUs may internally handle these state
| transitions better. I suppose its inclusion is recommended
| to ensure correctness across all CPUs.
|
| It may happen because without "dsb nsh" (Data
| Synchronization Barrier) and "isb" (Instruction
| Synchronization Barrier), QEMU may continue execution
| before the syscall fully completes, which causes a segfault
| or UB, but I am not entirely sure.
|
| "dsb nsh" and "isb" ensures correct execution order which
| prevents speculative execution issues.
| pm215 wrote:
| That doesn't sound right -- QEMU always works one
| instruction at a time, so we never start executing a
| following insn until the previous one is completed.
| oguz-ismail wrote:
| Yeah the segfault doesn't happen on other OSes, I believe
| it's an OpenBSD thing
| johnisgood wrote:
| So what causes QEMU to segfault without the
| synchronization? I have not tested it myself, however, so
| I cannot guarantee that it indeed does segfault.
| jmillikin wrote:
| The "dsb nsh; isb" sequence after "svc 0" is part of
| OpenBSD's mitigations for Spectre.
|
| https://github.com/openbsd/src/commit/bbeaada4689520859307d
| 5...
|
| https://github.com/openbsd/src/commit/0c401ffc2a2550c32105c
| e...
|
| https://github.com/openbsd/src/commit/5ecc9681133f1894e81c3
| 8...
|
| If I'm reading the commits correctly, the OpenBSD kernel
| will skip two instructions after a "svc 0" when returning
| to userspace, on the assumption that any syscall comes from
| libc and therefore has "dsb nsh; isb" after it.
| rollcat wrote:
| 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 wrote:
| And the MacOS API is similar.
| rollcat wrote:
| 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).
| nxobject wrote:
| That's a very fair assessment of Apple - IIRC a lot of
| early idiosyncratic hardening work in XNU world was done
| for the launch of the iPhone OS App Store, but it's only
| been (relatively) recently that they've started long-term
| initiatives systematically harden macOS on top.
| fwsgonzo wrote:
| 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.
| sim7c00 wrote:
| 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 wrote:
| Actually nowadays ideally we would already caught up with
| ESPOL/NEWP from 1961 and use only intrisics.
|
| Apparently that is a VC++ only thing.
| linksnapzz wrote:
| MCP and the rest of the Burroughs Large Systems are an
| interesting road not taken.
| LegionMammal978 wrote:
| > 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.
| fuhsnn wrote:
| Probably the current state that GCC for Aarch64 and RISC-V
| simply don't offer individual register constraints like x86.
| fwsgonzo wrote:
| Register variables are idiomatic when you're locking down
| every single register anyway, which is always the case for
| system calls. If you're writing assembly where you want the
| register allocators help, by all means use regular inline
| assembly.
| saagarjha wrote:
| I'd really prefer using inline assembly tbh
| fwsgonzo wrote:
| This is inline assembly.
| saagarjha wrote:
| Normal inline assembly not the register pinning you're
| doing
| armitron wrote:
| Idiomatic to you perhaps, to me this looks ridiculous and
| unnecessary, bordering on masochistic.
| sim7c00 wrote:
| 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
___________________________________________________________________
(page generated 2025-03-12 23:01 UTC)