|
|
Subscribe / Log in / New account

A pair of Rust kernel modules

A pair of Rust kernel modules

Posted Sep 13, 2022 10:33 UTC (Tue) by ncm (guest, #165)
In reply to: A pair of Rust kernel modules by koverstreet
Parent article: A pair of Rust kernel modules

Or, you could code it in C++, correctly, and be done.

Coding it in Rust does not, in fact, guarantee it is correct. Coding C++, you need only choose known-correct primitives to get the same level of assurance. But you would then face rabid, unreasoned hostility from Linus for C++ features you may use in Rust code without approbation; and have your patch summarily rejected with, most likely, a rude remark.

The easy way to get C++ code into the kernel is via eBPF, which offers solid support for building from C++, and where there is nothing Linus can do to stop you. And, it is exactly as safe as Rust eBPF, but likely less annoying to code.


to post comments

A pair of Rust kernel modules

Posted Sep 13, 2022 11:08 UTC (Tue) by gspr (guest, #91542) [Link] (2 responses)

> Or, you could code it in C++, correctly, and be done.

I believe that the parent commenter meant that doing exactly this is signficantly harder than it is in Rust.

A pair of Rust kernel modules

Posted Sep 13, 2022 11:13 UTC (Tue) by milesrout (subscriber, #126894) [Link] (1 responses)

It's far easier than in Rust, because in Rust it's impossible.

Please

Posted Sep 13, 2022 11:16 UTC (Tue) by corbet (editor, #1) [Link]

Can we please try to avoid yet another round of silly language-advocacy postings here? We've all heard it. The article is about people showing real work, albeit at an early stage; let's focus on the actual work.

Thank you all.

A pair of Rust kernel modules

Posted Sep 14, 2022 10:56 UTC (Wed) by xav (guest, #18536) [Link]

> Coding it in Rust does not, in fact, guarantee it is correct

To a certain extend, it does out of unsafe blocks, and way more than C++ can even dream of.

A pair of Rust kernel modules

Posted Sep 15, 2022 19:14 UTC (Thu) by nix (subscriber, #2304) [Link] (7 responses)

The easy way to get C++ code into the kernel is via eBPF, which offers solid support for building from C++
Solid? Earlier this year I had a persistent failure getting this to verify (under 5.16):
        uint32_t id;mp;
        dt_bpf_specs_t zero;

        __builtin_memset(&zero, 0, sizeof (dt_bpf_specs_t));

        for (id = 1; id <= 16; id++) {
                if (bpf_map_update_elem(&specs, &id, &zero,
                                        BPF_NOEXIST) == 0)
                        return id;
        }
A simple loop updating sixteen map values to 0 was rejected... because the verifier could not prove that this incredibly subtle and difficult loop would terminate. (I unrolled it, and everything was fine.)

Trying to write nontrivial C++ code and expecting it to turn to working eBPF without massive amounts of verifier agony is an exercise in futility at present. You can try if you like, but I wouldn't want to listen to the resulting cursing if you did. (Maybe this will change in time, but I very much suspect that turning the verifier into something that isn't full of arbitrary restrictions and can actually verify real code that wasn't written with 90% of one's attention on contorting things in unnatural ways to get it to verify will either slow it to unusability or just slam straight into the wall of Rice's theorem.)

A pair of Rust kernel modules

Posted Sep 15, 2022 19:37 UTC (Thu) by rahulsundaram (subscriber, #21946) [Link] (1 responses)

> Solid? Earlier this year I had a persistent failure getting this to verify (under 5.16):

Have you tried the Rust ones? https://aya-rs.dev/ or https://crates.io/crates/bcc

A pair of Rust kernel modules

Posted Sep 15, 2022 21:13 UTC (Thu) by nix (subscriber, #2304) [Link]

No, but the problem was the *verifier*, not the input language. The same sort of thing written in raw eBPF was also rejected. (It worked if the loop bound was < 4. How... useful.)

Figuring out why required diving into the verifier source code, because there is of course nothing like a spec, and the verifier's verification contains enough holes where obviously valid code is rejected out of hand because nobody's written code to verify it (if you're lucky, there are comments noting the lack of said verification) that it frankly should be called a collection of holes with a little fabric connecting them :)

A pair of Rust kernel modules

Posted Sep 16, 2022 8:51 UTC (Fri) by nybble41 (subscriber, #55106) [Link] (1 responses)

> Earlier this year I had a persistent failure getting this to verify (under 5.16): …

Strictly speaking, without taking into account knowledge of the internals of bpf_map_update_elem(), that loop might *not* terminate. You're passing in a pointer to the loop counter (id) and the function could potentially update the variable via that pointer to prevent it from incrementing. (Yes, even if the pointer argument is const-qualified; you can cast away the const qualifier on a pointer with const_cast as long you're not trying to store into a const object, and the variable id in this example isn't a const object.)

It would probably compile if you copied id into a separate local variable and passed a pointer to that variable to bpf_map_update_elem(). Then the compiler could easily prove that the function doesn't update id since it doesn't have its address (given the standard pointer provenance rules), which places a fixed upper bound on the number of loop iterations.

A pair of Rust kernel modules

Posted Sep 16, 2022 9:22 UTC (Fri) by Wol (subscriber, #4433) [Link]

Or bpf could take fortran to heart, and the definition in the spec that says "for loops do not guarantee that modifications to the index variable will "stick"".

The standard explicitly permits moving the index into a register (which other code has no access to) and then it saves it to the variable whenever it increments or other fancy tricks.

Whatever, as far as the code *inside* the loop is concerned, the index variable is either read-only, or if you do try and modify it, it's undefined.

So, provided the naive loop terminates, you can guarantee that any loop will terminate.

Cheers,
Wol

A pair of Rust kernel modules

Posted Sep 23, 2022 9:56 UTC (Fri) by rep_movsd (guest, #100040) [Link] (2 responses)

It looks like the id loop iterator is being passed by pointer to a function.
Why would a function need a primitive value passed in by pointer unless it intended to modify it?

The compiler cannot guarantee that id doesnt change whilst you are looping.

So either the function takes a const pointer to int (which is braindead) and the compiler is brain dead not to see its not modifiable

OR

The function takes it by pointer (which may be braindead) and the compiler is right

A pair of Rust kernel modules

Posted Sep 23, 2022 11:05 UTC (Fri) by adobriyan (subscriber, #30858) [Link]

"key" should be pointer to const.

BPF_CALL_4(bpf_map_update_elem,
struct bpf_map *, map,
void *, key,
void *, value,
u64, flags)
{
WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_bh_held());
return map->ops->map_update_elem(map, key, value, flags);
}

A pair of Rust kernel modules

Posted Sep 23, 2022 18:04 UTC (Fri) by nybble41 (subscriber, #55106) [Link]

> So either the function takes a const pointer to int (which is braindead) and the compiler is brain dead not to see its not modifiable

Even if the function did take a const-qualified pointer, it wouldn't matter. It's perfectly legal to do something like this:

void f(int const *p) { *(int*)p = 7; }

... as long as the original *object* was not const-qualified. (Attempting to write into a *variable* which was const-qualified at the point of declaration is, of course, undefined behavior, whether or not the pointer being used is const-qualified.) Consequently, the compiler can't assume that the function won't write into a mutable object even if the pointer argument is const-qualified.

Also, the read-only pointer-to-int argument pattern isn't quite so "braindead" as you suppose. These functions are generic; in this case the key happens to be an int, but that isn't always true, and so the key is passed by reference rather than value. The comparison callback for the standard library's qsort() function uses the same pattern, for example, when sorting an int array.


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