10

Quoting from https://www.kernel.org/doc/Documentation/process/adding-syscalls.rst:

At least on 64-bit x86, it will be a hard requirement from v4.17 onwards to not call system call functions in the kernel. It uses a different calling convention for system calls where struct pt_regs is decoded on-the-fly in a syscall wrapper which then hands processing over to the actual syscall function. This means that only those parameters which are actually needed for a specific syscall are passed on during syscall entry, instead of filling in six CPU registers with random user space content all the time (which may cause serious trouble down the call chain).

What serious trouble down the call chain is the last parenthesized clause referring to?

To me it seems stupid not to load the six registers in the generic leadup to the syscall. Forcing each syscall wrapper to do it makes them larger and the syscall funcs become a new special case, so I'm wondering what the "serious trouble" is with having unintentional user content in unused argument registers.

3
  • 1
    What do you mean by "Forcing each syscall wrapper to do it makes them larger"? The syscall wrappers are already there even without SYSCALL_PTREGS, the new selective register unpacking is generated by a macro (so no extra coding work) and the wrappers are inlined, so the resulting assembly is almost the same as what the compiler would generate previously. If anything, unpacking only a subset of the registers generates less code.
    – TooTea
    21 hours ago
  • @TooTea With the ptregs style argument passing you'd expect some codesize increase due to each syscall table entry needing to do the unpacking for itself. On x86-64, that's something like 4-byte mov per argument in each syscall. When you upack all sigs regs in the shared lead up the syscall, you pay that codesize cost once for all syscalls. A little more code gets executed if argcnt <6 (though that's cheap code--loading a small number of regs from cached memory is hardly worth fussing about even in userspace much less after paying a huge price per ring transition), but codesize is saved. 21 hours ago
  • @TooTea The codesize difference is six offseted movs times 1 (~ 24 bytes) vs 0-6 offsetted movs times number of syscalls. 21 hours ago

1 Answer 1

11

One of the concerns wasn’t so much with arbitrary register values, but that they get copied to the kernel stack. Unused registers can thus be used to write arbitrary caller-controlled values to the stack, with no checks.

These values on the stack could potentially be used in a more complex attack. That’s why removing this possibility seemed like a good idea.

Kees Cook’s 4.17 summary also mentions possible influence of these register values on speculative execution.

5
  • OpenBSD went futher and removed syscall(2) completely.
    – Kusalananda
    yesterday
  • 2
    @Kusalananda IMO that’s not quite the same — the issue addressed here is that legitimate syscalls from legitimate call sites could be fed extra data in registers. Dropping syscall wouldn’t avoid that — it would enable “allowed” callers to perform their own cleanup, but from the kernel’s perspective it’s best to clean things up on the inside anyway. yesterday
  • Thank you for explaining that. I confess to being easily confused about kernel internals no matter what Unix, and I naively thought the issues were the same.
    – Kusalananda
    yesterday
  • Oh they’re in a similar vein, ensuring that entry into the kernel is as limited as possible ;-). yesterday
  • 1
    @Kusalananda Linux does syscall(2) entirely in userspace, whereas the BSDs have an actual syscall(2) syscall in the kernel instead.
    – OrangeDog
    yesterday

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .