HNHacker News
TopNewBestAskShowJobs

max_k

784 karma · joined December 21, 2017

C++ hacker
submissionscomments
max_k··on C++20 coroutines and io_uring
> do you use exceptions?

Exceptions, sorry.

I learned C++ in the early 90ies, but didn't like it much; I needed to do plain C for a few years at dayjob and several open source projects until I was fed up with manual error handling and go back to C++. At first with a "-fno-exceptions" policy (using something similar to GLib's GError), but gave up my resistance after a few more years, and now I enjoy exceptions very much. It's not perfect, nothing is, but everything else is uglier and so much more cumbersome.

Exceptions seem inappropriate with writing non-blocking/asynchronous code, because where do you throw stuff when the caller is not on the call stack, but instead wants you to invoke a completion callback... but on the other hand, I don't want to implement two different kinds of error reporting - just look at the C++ standard library, which sometimes throws exceptions, sometimes uses std::errc - no, I wanted one single way to wrap error conditions, and I decided that std::exception_ptr is the way to go. All my error callbacks take an std::exception_ptr parameter, which they can then rethrow eventually, or pass on to the next error callback. Yes, I hate std::exception_ptr because it allocates memory on the heap, I despise implicit dynamic allocations, but everything else is uglier and so much more cumbersome. (I repeat myself.)

Error handling is dirty, no matter how you solve it, but C++ exceptions, with all their disadvantages, allow me to just consider the problem solved and go on with writing real code instead of keeping worrying everywhere. It just works.

I know many projects and corporations have a strict "-fno-exceptions" policy and will not change their minds like I did - that's a matter of personal taste.

max_k··on C++20 coroutines and io_uring
> Would you consider adding a kqueue implementation?

Can't do, I don't have Apple or BSD anywhere. But if code for it were submitted to me, I would gladly merge it (and try to keep the CI happy).

The MPD version of the event loop is portable and runs on macOS/BSD/Windows, but no kqueue, only poll() (and select() on Windows). This old-school API has its scalability problems, of course, but that matters not so much for MPD.

The event loop can already do Coroutines on all supported targets; here's another open source project where I use this library: https://github.com/XCSoar/XCSoar/tree/master/src/event/ - it's a flight computer (yes, for real airplanes) which also runs on Windows and macOS and iOS, and runs libcurl (and other stuff) as coroutine.

If you want to use my library and need help with integrating it or with adding kqueue support, get in touch with me.

max_k··on C++20 coroutines and io_uring
Whoa, that exists? How about having some nerd fun and we create a PR for each other's open source project?
max_k··on C++20 coroutines and io_uring
I love C++ coroutines! (The spec has a few warts and gives me enough reasons to hate them, like not being able by definition to properly inline nested coroutine calls, but I love them anyway.)

I've written several libraries for integrating C++ coroutines with stuff like io_uring, libcurl, c_ares, libpq and more. For example, this is how using my io_uring/coroutine library can be used: https://github.com/CM4all/libcommon/blob/master/test/co/RunC...

  auto result = co_await CoReadTextFile(queue, AT_FDCWD, path);
  co_await CoWrite(queue, STDOUT_FILENO, result.data(), result.size(), 0);
This opens a file, stats it, reads its contents, and writes it to stdout - all 4 I/O operations are asynchronous with io_uring. Source code for CoReadTextFile() which is also a coroutine: https://github.com/CM4all/libcommon/blob/master/src/io/uring...

Sample code for libpq: https://github.com/CM4all/libcommon/blob/master/test/co/RunC... and c_ares https://github.com/CM4all/libcommon/blob/master/test/co/RunC... and libcurl https://github.com/CM4all/libcommon/blob/master/test/curl/Ru...

I wrote all of this for proprietary applications at dayjob, but the core library is open source, as is much of my dayjob code. The I/O event loop this integrates with is also used by several open source projects I maintain, e.g. the Music Player Daemon (https://github.com/MusicPlayerDaemon/MPD/tree/master/src/eve...) which can also take advantage of io_uring, though not (yet) with coroutines, only "classic" non-blocking I/O.

My code is optimized for low-overhead; the very core doesn't even use std::function because I fear its implicit heap allocations. Long ago, I used boost::asio (which also integrates well with coroutines) but didn't like it because it was too bloated for me.

I've rarely seen other nerds talk about C++ coroutines, and never about integrating them with io_uring, made me thinking I'm the only one. But maybe all the others just don't write/blog about it - I never did either... That's why this blog was a refreshing read for me, thanks.

max_k··on Dirty Pipe Explained
No, the disk is not involved in this vulnerability. It happens only in RAM (the page cache).
max_k··on Dirty Pipe Explained
There's also "anonymous memory" (as opposed to memory mapped from a file, which you could call "named memory").
max_k··on Dirty Pipe Explained
> pipe flag “PIPE_BUF_FLAG_CAN_MERGE”, which signifies that the data buffer inside the pipe can be merged, i.e, this flag notifies the kernel that the changes which are written to the page cache pointed to by the pipe shall be written back to the file

That's not what this flag does. No pipe flag can ever cause writing dirty pages back to disk, because pipes have by definition nothing to do with files.

The CAN_MERGE flag tells the kernel that the next write() to the pipe can append data to this pipe buffer until it's full, instead of creating a new pipe buffer for every write().

max_k··on Dirty Pipe Explained
Took a long while until I found the right state of mind to finally understand what was causing those (rare) file corruptions. Once I figured that out, the rest was easy :-)
max_k··on The Dirty Pipe Vulnerability
Then let's have those 2 or 3 documented init functions. That's not perfect, but still much better than spraying different (undocumented) copies of the init code everywhere, that have to be located and adjusted every time somebody refactors something.
max_k··on The Dirty Pipe Vulnerability
> I don't think you have much insight into this.

I don't think you know who wtarreau is.

max_k··on The Dirty Pipe Vulnerability
What does "streaming buffers" mean? splice() avoids copying data from kernel to userspace and back; it stays in the kernel, and often isn't even copied at all, only page references are passed around.
max_k··on The Dirty Pipe Vulnerability
Yes, but as you said, it works only after adding such annotations to various libraries. A circular buffer is just a special kind of memory allocator, and as such, when it allocates and deallocates memory, it needs to tell the sanitizer about it.

What bothers me about the Linux code base is that there is so much code duplication; the pipe doesn't use a generic circular buffer implementation, but instead rolls its own. If you had the one true implementation, you'd add those annotations there, once, and all users would have it, and would benefit from KMSAN's deep insight.

Every time I hack Linux kernel code, I'm reminded how ugly plain C is, how it forces me to repeat myself (unless you enter macro hell, but Linux is already there). I wish the Linux kernel would agree on a subset of C++, which would allow making it much more robust and simpler.

They recently agreed to allow Rust code in certain tail ends of the code base; that's a good thing, but much more would be gained from allowing that subset of C++ everywhere. (Do both. I'm not arguing against Rust.)

max_k··on The Dirty Pipe Vulnerability
Interesting compiler feature to work around (unknown) vulnerabilities similar to this one. However in this case, it wouldn't help; the initial allocation is with explicit zero-initialization, but this is a circular buffer, and the problem occurs when slots get reused (which is the basic idea of a circular buffer).
max_k··on The Dirty Pipe Vulnerability
Agree, but even 100% test coverage can't catch this kind of bug. I don't know of any systematic testing method which would be able to catch it. Maybe something like valgrind which detects accesses to uninitialized memory, but then you'd still have to execute very special code paths (which is "more" than 100% coverage).
max_k··on The Dirty Pipe Vulnerability
There's no bug in that commit, the commit is correct, it only makes the bug exploitable. The buggy commit is older, it's https://github.com/torvalds/linux/commit/241699cd72a8489c944... but not exploitable.

> I always try to shy away from making things look nicer

That's understandable, though from my experience, lots of old bugs can be found while refactoring code, even at the (small) risk of introducing new bugs.

max_k··on The Dirty Pipe Vulnerability
Your post sounds like it's a bad thing, but "nicer" code is easier to maintain, i.e. there will be fewer bugs (and fewer vulnerabilities). This bug is an exception of the rule - shit happens. But refactoring code to be "nicer" prevents more bugs than it causes. Two patches were involved in making this bug happen, and minus the bug, I value both of them (and their authors).
max_k··on The Dirty Pipe Vulnerability
btw. this is how I would make the code more robust: https://lore.kernel.org/lkml/20220225185431.2617232-4-max.ke...

I'm a C++ guy, and the lack of constructors is one of many things that bothers me with C.

max_k··on The Dirty Pipe Vulnerability
Yes, that's what I did.
max_k··on The Dirty Pipe Vulnerability
Yes. I have a working exploit, but havn't published it (yet).
max_k··on The Dirty Pipe Vulnerability
> require all fields to be initialized any time an object is created

I'm not a fan of such a policy. That usually leads to people zero-initializing everything. For this bug, this would have been correct, but sometimes, there is no good "initial" value, and zero is just another random value like all the 2^32-1 others.

Worse, if you zero-initialize everything, valgrind will be unable to find accesses to uninitialized variables, which hides the bug and makes it harder to find. If I have no good initial value for something, I'd rather leave it uninitialized.

max_k··on The Dirty Pipe Vulnerability
Yes. But you can also inject code into libc.so.6, and all running processes will have it.
← PreviousPage 2 of 2