|
|
Subscribe / Log in / New account

ioctl() forever?

ioctl() forever?

Posted Jun 8, 2022 20:07 UTC (Wed) by wahern (subscriber, #37304)
In reply to: ioctl() forever? by jhoblitt
Parent article: ioctl() forever?

> Protobufs, as an API specification, are much easier to machine process than the entirety of the C language.

With CTF/BTF symbols, sticking to C would be easier and simpler for all involved, especially when you begin to look at what would be the most difficult part of structured, compound, nested data type filtering--actual filter specifications and execution. The obvious solution is a BPF program, which is precisely why BTF symbols were added to the kernel--so BPF programs could introspect kernel data types.


to post comments

ioctl() forever?

Posted Jun 8, 2022 20:25 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

Even with CTF you still have problems with strings.

ioctl() forever?

Posted Jun 9, 2022 13:37 UTC (Thu) by nix (subscriber, #2304) [Link] (2 responses)

Yeah, but that's neither CTF nor BTF's fault: that's the same problem with dereferencing pointers (and finding a way to do that while not allowing attackers to modify the thing and sneak bad stuff past you via TOCTTOU races, *without* incurring horrible overhead by forcing every such pointed-to thing to be CoWed on modification).

ioctl() forever?

Posted Jun 9, 2022 18:44 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

That's why just marshalling all the data into variable-length structures that can be simply copied into the kernel space is the right way to do it. Yes, you have some overhead duet to packing and copying, but both are pretty efficient.

Also, ioctl()s shouldn't be performance-critical anyway.

ioctl() forever?

Posted Jun 9, 2022 23:14 UTC (Thu) by wahern (subscriber, #37304) [Link]

With CTF symbols you could easily write a simple function to recursively copy a data structure into kernel space. So you end up in the same place, but with a fraction of the complexity and code. For C string members you can trivially implement constraint checking during the copy. Similarly, for dependent pointer and size members you can include simple qualifiers in the structure definition that the copy routine can validate. (There was a proposal, effectively implementing a simple dependent type system for this case, which came close to passing muster for the next C2x revision. It seems to have failed for lack of real-world implementation experience, but strictly speaking it should be quite simple to implement.)

Alternatively, you could implement a type-safe unpacking API. (If you only care about memory safety, you might not even need CTF.) For example, "give me the next member, which I expect to be a NUL-terminated string". This may be more or less ergonomic than the above, but either way would provide the same effective interface as generic deserializers--even "zero copy" serialization formats typically cannot be exposed as plain C data structures, even when the serialization and deserialization code is generated a la Cap'n Proto. The difference between `string_t getNamedField(object_t)` and `string_t copy_in_string(userstruct_t *, cursor_t *)` is purely syntactical when you can't completely trust the input to be well formed, which we can't.

In any event, simple intermediations, with or without CTF, provide avenues to enjoying all the same safety guarantees, without creating both kernel and userland dependencies on enormous (10-100+k SLoC) libraries. Microkernels tend to heavily rely on serialized message passing and/or RPC code generators. I'm sure they work well, but there's a steep upfront cost, especially when it comes to tooling.

Maybe even a simpler alternative is it avoid variable length strings entirely. Briefly looking at existing ioctls, it seemed like most string fields within structures are fixed-sized arrays. Variable length strings seemed more likely to be passed directly as an argument. (But please feel free to set me straight on that account as I didn't look very hard and certainly didn't write any tools to analyze the types.) To the extent the latter are a problem, they could be fixed with a very thin intermediating interface. There's something to be said for simply using fixed-sized arrays. During the heyday of writing GNU replacements for proprietary tools, there was an emphasis on removing arbitrary limitations and permitting every conceivable type of input to be variable length and unbounded; that emphasis became pervasive and reflected in even trivial, internal interfaces removed from user input. That's rarely needed, and rarely warranted given the resulting complexity, *especially* for kernel interfaces. Even file paths (or more specifically, file path arguments) have a fixed upper bound in the Linux kernel. Recently I discovered this was true even in Solaris, despite Solaris have a complicated kernel syscall facility for unbounded-length input and output syscall arguments. If you step away from trying to making everything configurable and unbounded, then it becomes much easier to limit complexity. Usually you can set an upper bound that is good enough and move on; and for many of the exceptions, you can switch to semantics that let you trade time for space (i.e. trade serial processing for discrete buffers). Or to put it another way, for the rare cases where setting fixed-bound arguments is too cumbersome, use netlink instead of ioctl. Problem solved. One doesn't need to become the other, or replaced with something fancier; just make the choice more clear.

If you want to make it easier to prevent people from accidentally doing the wrong thing, and to identify places where that might be likely, you can add type annotations to structures and other types used by kernel ioctl interfaces, complemented with a GCC module pass that identifies code that directly access members. IOW, you can implement something akin to Rust's type checker that prevents normal code from reading pointers in an unstructured manner, forcing that code to go through a "safe" API.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds