Live data from Hacker News

lsr: ls with io_uring

rockorager.dev

111–120 of 177 posts

Re: lsr: ls with io_uring

#111

Love it. I'm trying to understand why all command line tools don't use io_uring. As an example, all my nvme's on usb 3.2 gen 2 only reach 740MB/s peak. If I use tools with aio or io_uring I get 1005MB/s. I know I may not be copying many files simultaneously every time, but the queue length strategies and the fewer locks also help I guess.

> I'm trying to understand why all command line tools don't use io_uring.

Because it's fairly new. The coreutils package which contains the ls command (and the three earlier packages which were merged to create it) is decades old; io_uring appeared much later. It will take time for the "shared ring buffer" style of system call to win over traditional synchronous system calls.

Re: lsr: ls with io_uring

#112
post #50

The times seem sublinear, 10k files is less than 10x 1k files. I remember getting in to a situation during the ext2 and spinning rust days where production directories had 500k files. ls processes were slow enough to overload everything. ls -F saved me there. And filesystems got a lot better at lots of files. What filesystem was used here? It's interesting how well busybox fares, it's written for size not speed iirc?

> The times seem sublinear, 10k files is less than 10x 1k files Two points are not enough to say it's sublinear. It might very well be some constant factor that becomes less and less important the bigger the linear factor becomes. Or in other words 10000 n+C (n+C)

The article has data points for n=10,100,1000,10000. Taking (n=10,000 - n=10)/(n=1,000 - n=10) would eliminate the constant factor and we'd expect about 10.09x higher times for a linear algorithm.

But for lsr, it's 9.34. The other tools have factors close to 10.09 or higher. Since ls has to sort it's output (unless -F is specified) I'd not be too surprised with a little superlinearity.

https://docs.google.com/spreadsheets/d/1EAYua3B3UeTGBtAejPw2...

Re: lsr: ls with io_uring

#113

There used to be lsring by Jens Axboe (author of io_uring), but it no longer exists. This is more extreme than abandoning the project. Perhaps there is some issue with using io_uring this way, perhaps vulnerabilities are exposed.

> Perhaps there is some issue with using io_uring this way, perhaps vulnerabilities are exposed. ... no. It's just not interesting or particularly valuable to optimize ls, and Jens probably just used it as a demo and didn't want to keep it around.

Explicit Vulnerabilities (Documented CVEs and Exploits)

These are actual discovered vulnerabilities, typically assigned CVEs and often exploited in sandbox escapes or privilege escalations: 1. CVE-2021-3491 (Kernel 5.11+)

    Type: Privilege escalation

    Mechanism: Failure to check CAP_SYS_ADMIN before registering io_uring restrictions allowed unprivileged users to bypass sandboxing.

    Impact: Bypass of security policy mechanisms.
2. CVE-2022-29582

    Type: UAF (Use-After-Free)

    Mechanism: io_uring allowed certain memory structures to be freed and reused improperly.

    Impact: Local privilege escalation.
3. CVE-2023-2598

    Type: Race condition

    Mechanism: A race in the io_uring timeout code could lead to memory corruption.

    Impact: Arbitrary code execution or kernel crash.
4. CVE-2022-2602, CVE-2022-1116, etc.

    Type: UAF and out-of-bounds access

    Impact: Escalation from containers or sandboxed processes.
5. Exploit Tooling:

    Tools like io_uring_shock and custom kernel exploits often target io_uring in container escape scenarios (esp. with Docker or LXC).
Implicit Vulnerabilities (Architectural and Latent Risks)

These are not necessarily exploitable today, but reflect deeper systemic design risks or assumptions. 1. Shared Memory Abuse

    io_uring uses shared rings (memory-mapped via mmap) between kernel and user space.

    Risk: If ring buffer memory management has reference count bugs, attackers could force races, data corruption, or misuse stale pointers.

 2. User-Controlled Kernel Pointers

    Some features allow user-specified buffers, SQEs, and CQEs to reference arbitrary memory (e.g. via IORING_OP_PROVIDE_BUFFERS, IORING_OP_MSG_RING).

    Risk: Incomplete validation could allow crafting fake kernel structures or triggering speculative attacks.

 3. Speculative Execution & Side Channels

    Since io_uring relies on pre-submitted work queues and long-lived kernel threads, it opens timing side channels.

    Risk: Predictable scheduling or timing leaks, esp. combined with hardware speculation (Spectre-class).

 4. Bypassing seccomp or AppArmor Filters

    io_uring operations can effectively batch or obscure syscall behavior.

    Example: A program restricted from calling sendmsg() directly might still use io_uring to perform similar actions.

    Risk: Policy enforcement tools become less effective, requiring explicit io_uring filtering.

 5. Poor Auditability

    The batched and asynchronous nature makes logging or syscall audit trails incomplete or confusing.

    Risk: Harder for defenders or monitoring tools to track intent or detect misuse in real time.

 6. Ring Reuse + Threaded Offload

    With IORING_SETUP_SQPOLL or IORING_SETUP_IOPOLL, I/O workers can run in kernel threads detached from user context.

    Risk: Desynchronized security context can lead to privileged operations escaping sandbox context (e.g., post-chroot but pre-fork).

 7. File Descriptor Reuse and Lifecycle Mismatch

    Some operations in io_uring rely on fixed file descriptors or registered files. Race conditions with FD reuse or closing can cause inconsistencies.

    Risk: UAF, type confusion, or logic bombs triggered by kernel state confusion.

 Emerging Threat Vectors
 eBPF + io_uring

    Some exploits chain io_uring with eBPF to do arbitrary memory reads or writes. e.g., io_uring to perform controlled allocations, then eBPF to read or write memory.

 io_uring + userfaultfd

    Combining userfaultfd with io_uring allows very fine-grained control over page faults during I/O — great for fuzzing, also for exploit primitives.

Re: lsr: ls with io_uring

#114
post #61

Why isn’t it possible — or is it — to make libc just use uring instead of syscall? Yes I know uring is an async interface, but it’s trivial to implement sync behavior on top of a single chain of async send-wait pairs, like doing a simple single threaded “conversational” implementation of a network protocol. It wouldn’t make a difference in most individual cases but overall I wonder how big a global speed boost you’d…

Not speaking of ls which is more about metadata operations, but general file read/write workloads: io_uring requires API changes because you don't call it like the old read(please_fill_this_buffer). You maintain a pool of buffer that belong to the ringbuffer, and reads take buffers from the pool. You consume the data from the buffer and return it to the pool. With the older style, you're required to maintain O(pendin…

In a single threaded flow your buffer pool is just the buffer you were given, and you don't return until the call completes. There are no actual concurrent calls in the ring. All you're doing is using io_uring to avoid syscall.

Other replies lead me to believe it's not worth doing though, that it would not actually save syscalls and might make things worse.

Re: lsr: ls with io_uring

#115
post #69
post #61

Why isn’t it possible — or is it — to make libc just use uring instead of syscall? Yes I know uring is an async interface, but it’s trivial to implement sync behavior on top of a single chain of async send-wait pairs, like doing a simple single threaded “conversational” implementation of a network protocol. It wouldn’t make a difference in most individual cases but overall I wonder how big a global speed boost you’d…

In addition to sibling's concern about syscall amplification, the async just isn't useful to the application (from a latency perspective) if you just serialize a bunch of sync requests through it.

[deleted]

Re: lsr: ls with io_uring

#116

Author of the project here! I have a little write up on this here: https://rockorager.dev/log/lsr-ls-but-with-io-uring

(Thanks - we'll make that the main link (since it has more background info) and include the repo thread at the top as well.)

Re: lsr: ls with io_uring

#117

Kind of fascinating that slashing syscalls by ~35x (versus the `ls -la` benchmark) is "only" worth a 2x speedup

I vaguely remember some benchmark I read a while back for some other io_uring project, and it suggested that io_uring syscalls are more expensive than whatever the other syscalls were that it was being used to replace. It's still a big improvement, even if not as big as you'd hope.

I wish I could remember the post, but I've had that impression in the back of my mind ever since.

Re: lsr: ls with io_uring

#118
post #77

Earlier quoted context omitted.

In order to make this work, libc would have to: - Start some sort of async executor thread to service the io_uring requests/responses - Make it so every call to "normal" syscalls causes the calling thread to sleep until the result is available (that's 1 syscall) - When the executor thread gets a result, have it wake up the original thread (that's another syscall) So you're basically turning 1 syscall into 2 in order…

You don't need to start spawning new threads to use io_uring as a backend for synchronous IO APIs. You just need to set up the rings once, then when the program does an fwrite or whatever, that gets implemented as sending a submission queue entry followed by a single io_uring_enter syscall that informs the kernel there's something in the submission queue, and using the arguments indicating that the calling process wa…

> using the arguments indicating the calling process wants to block

Nice to know io_uring has facilities for backwards compatibility with blocking code here. But yeah, that's still a syscall, and given that the whole benefit of io_uring is in avoiding (or at least, coalescing) syscalls, I doubt having libc "just" use io_uring is going to give any tangible benefit.

Post reply on HN