News for hackers. Stuff that matters.
Re:OpenBSD – pinning all system calls by tiffanyh on Monday December 11, 2023 @08:58PM (#38608315)
Would you mind sharing how and where in the code, specifically.

Geniunely curious.

Re:OpenBSD – pinning all system calls by ninjin on Monday December 11, 2023 @09:52PM (#38608738)
Re:OpenBSD – pinning all system calls by hedora on Monday December 11, 2023 @10:22PM (#38608996)
One of the more under-appreciated features of Rust is that it traps on integer overflow / underflow by default.

It’s too bad OpenBSD doesn’t have a good Rust story for the core system. (I understand their reasoning, but it’s still too bad.)

I wonder how hard it would be to backport the integer behavior to C. (C++ would be easy: Use templates.) Perhaps they could add a compiler directive that causes vanilla integer overflow/wraparound to trap, then add annotations or a special library call that supports modulo arithmetic as expected.

Re:OpenBSD – pinning all system calls by ninjin on Monday December 11, 2023 @10:37PM (#38609098)
Look, I get as much as the next guy that Rust brings a lot of niceties. But what we are talking about here is a project that clocks in at just over 19,000,000 lines of C (`wc -l $(find src -name '*.c' -or -name '*.h')`), code heritage going back more than 40 years, an explicit commitment to a very rich set of platforms [1], and a very limited amount of manpower compared to projects such as Linux with their insane level of corporate backing. "Just rewrite it in and/or integrate Rust" is neither easy nor safe in that it overturns everything that is already there and tested.

[1]: https://www.openbsd.org/plat.html

As for improving upon C. I can not speak for OpenBSD as a project, but I am sure that there would be ample excitement to produce a minimal, solid C compiler with experimental security features to then serve as the default compiler for the project (heck, OpenBSD already ships with a number of less common security-related compiler flags from what I recall). Sadly, I doubt there is either funding or the hands to make that a reality.

Re:OpenBSD – pinning all system calls by nextaccountic on Tuesday December 12, 2023 @12:35AM (#38609903)
This is all true, but this just means that any security focused OS for the next 40 years should consider Rust
Re:OpenBSD – pinning all system calls by bayindirh on Tuesday December 12, 2023 @02:24AM (#38610640)
Rust brings niceties, but this implies no requirement for considering Rust.
Re:OpenBSD – pinning all system calls by ninjin on Tuesday December 12, 2023 @06:59AM (#38611093)
Implying that OpenBSD has not considered Rust? There is plenty of discussion on misc@ already (for example [1]) and while it certainly can be "ranty", I am sure if one reads it in good faith you get a fairly nuanced picture.

[1]: https://marc.info/?l=openbsd-misc&m=151233210523661&w=2

Look, I think a lot of programmers and managers fail to understand the number of factors one needs to consider and how it scales with the complexity of your codebase and what it interacts with. If you want to rewrite your video processing service which you wrote together with five or so contributors and is say 20,000 lines of C++ into Rust, that is one thing. Take a step back and consider the number of users and what you interact with, it all seems rather manageable and you can probably be backwards compatible with respect to your users. In terms of time, maybe a few months? Maybe even six? For a single programmer that now needs to learn proper Rust.

Now, instead consider what an operating system is and the surface with which it interacts. The absolutely metric ton of hardware, the heap of standards, the massive load of hacks that are documented and undocumented, the large amount of users, the number of contributors and their experience, the platforms that you support, all the software that is written for your operating system to make it useful, etc. OpenBSD is famous (infamous?) for being willing to break things to do "the right thing". But they are also famous (infamous, again?) for being very conservative, which I think is understandable given their security focus and (relatively) low amount of manpower. Rust has certainly been considered, but it is far from the only consideration. This is akin to walking into a multi-million dollar company that has a fully functioning service or piece of software that they have been selling for decades and suggest to management that maybe we should start moving it all from C# to Rust next week, while being blissfully unaware of everything else the company is beholden to. Spoiler, it will not work out that way.

Furthermore, it is somewhat tiring that Rust keeps being touted as the final revelation when it comes to writing safer code. Guess what, there were plenty of projects before Rust that introduced safety in various forms (and at various costs) and there will be plenty of projects after Rust that will do the same. Yes, it is an amazing piece of technology, but it is equally plausible that its influence may not end up in it eating the world; rather give birth to something else or bring some of its thinking into other languages. Only time will tell and worse may end up being better yet again.

So to me, what any operating system (security focused or not) should do is to consider their (limited) options given their own goals and situation. Which is something I have seen plenty of evidence that a project of the age of OpenBSD is doing and successfully so.

Personally, I am keeping an eye on Redox and look forward to see what lessons will be learnt from their take of what an operating system can be. But for my servers and desktop I have and continue (for now?) to run a healthy mix of Linux and BSD while getting work done.

Re:OpenBSD – pinning all system calls by jcranmer on Tuesday December 12, 2023 @11:20AM (#38614248)
> I am sure if one reads it in good faith you get a fairly nuanced picture.

I don't see a lot of nuance. The primary argument in that thread boils down to any programmer who isn't using C isn't a serious programmer. The dominant technical analyses are a) adding more compiler toolchains make builds take a lot longer and b) memory safety doesn't protect against everything, so why bother? That's not nuance.

Nuance would be pointing out that Rust provides benefits over just memory safety: that Rust tends to push you towards a parse-don't-validate memory, or maybe Rust having a greater emphasis on error checking correctly [1]. But then again, Rust isn't necessary to do that, so you could also analyze how existing software development practices are sufficient to bring those into play.

You can also discuss Rust's failures. Rust, after all, didn't get uninitialized memory right. There's also some uneasiness about the details of the borrow checker to point to, or you can throw some remarks about the &mut-is-noalias and the difficulty it takes to actually ensure that you never create two &mut to the same location in unsafe code.

Nuance might also discuss how safety features do and don't percolate in mixed-programming-language environments, or how a rewrite may or may not improve security. There's definitely items on both sides of the cost-benefit ledger there!

But that's not what we got. The thread starts with an (incorrect [2]) gatekeeping moment of "it's not serious if you don't rewrite coreutils in it," and mostly devolves into a general theme of "anyone who needs their programming language to provide safety wheels is a terrible programmer who shouldn't be allowed anywhere near systems programming". The irony of that viewpoint coming from an OS well known for its love of just-in-case security precautions is not lost on me.

[1] I was recently writing something parsing diffs, and as part of that, I was making sure that the arithmetic on line numbers didn't overflow. Integer overflow (whether signed or unsigned) is frequently ignored as an error source in most programming languages!

[2] At the time the post was written, people were in fact working on doing a rewrite of this stuff in Rust.

Re:OpenBSD – pinning all system calls by ninjin on Tuesday December 12, 2023 @11:50AM (#38614726)
Thank you, that is an interesting response to read, but what I was trying to communicate with that sentence was that if you read that thread and others in good faith you can get a fairly nuanced picture and not that it is one in and of itself. I am certainly not implying that you get a nuanced picture from simply reading a mailing list which I described as being littered with rants in the very same sentence that you quoted parts of.

As for the linked thread itself, I just ignored the "Real Programmers Don't Use Pascal" parts (it gets old really fast) and what I arrived it is largely reflected in what I already wrote in the parent and ancestors.

Re:OpenBSD – pinning all system calls by nextaccountic on Tuesday December 12, 2023 @06:24PM (#38620260)
> You can also discuss Rust's failures. Rust, after all, didn't get uninitialized memory right.

I think that's a success story because while Rust 1.0 shipped with the hopelessly broken std::mem::unitialized, Rust 1.36 shipped with std::mem::MaybeUninit that fixed the situation. (it's true that they can't definitely remove the broken API, but it can be aggressively linted against nonetheless)

That is, it's a flaw present in the first version of the language but a success of the development process and language evolution, without breaking backwards compatibility

https://doc.rust-lang.org/std/mem/fn.uninitialized.html

https://doc.rust-lang.org/std/mem/union.MaybeUninit.html

Re:OpenBSD – pinning all system calls by nextaccountic on Tuesday December 12, 2023 @05:23PM (#38619549)
> Implying that OpenBSD has not considered Rust?

No, not at all. OpenBSD may consider Rust all the time and it may still be not the right language for them.

But Rust might be the right language for some greenfield projects (which means, no rewrite), in ways that doesn't apply to a decades-old OS

Re:OpenBSD – pinning all system calls by ninjin on Tuesday December 12, 2023 @08:11PM (#38621316)
Thanks, I do think we are largely in agreement. Despite me somehow needing a wall of text to communicate it all.
Re:OpenBSD – pinning all system calls by eb0la on Tuesday December 12, 2023 @07:13AM (#38611182)
IMHO the biggest blocker for Rust inside OpenBSD is that the project already has killed C developers and moving (some stuff) to Rust will need time and effort that could be put into other problems.

If they had to stabilize some parts, probably the answer would be different.

Re:OpenBSD – pinning all system calls by Narishma on Tuesday December 12, 2023 @11:57AM (#38614838)
> IMHO the biggest blocker for Rust inside OpenBSD is that the project already has killed C developers and moving (some stuff) to Rust will need time and effort that could be put into other problems.

I'm all for dropping C in favor of Rust for new projects, but killing C developers is going a bit too far, don't you think?

Re:OpenBSD – pinning all system calls by abound on Monday December 11, 2023 @10:37PM (#38609097)
> One of the more under-appreciated features of Rust is that it traps on integer overflow / underflow by default.

I think this is only true on debug builds, with --release (which most Rust binaries an end-user uses should be compiled with) it just wraps [1] [2]

[1] https://github.com/rust-lang/rfcs/blob/26197104b7bb9a5a35db2... [2] https://stackoverflow.com/a/60238510

Re:OpenBSD – pinning all system calls by yakubin on Tuesday December 12, 2023 @07:25AM (#38611287)
In this case more important than any runtime overflow/underflow checks is the fact that the compiler will check that comparison operands are of the same type instead of inserting an implicit cast. Instead the programmer is forced to insert an explicit conversion like .try_into().unwrap(), which clearly suggests the possibility of an error. And if the error isn't handled, it will panic.

https://play.rust-lang.org/?version=stable&mode=debug&editio...

Re:OpenBSD – pinning all system calls by yakubin on Tuesday December 12, 2023 @10:30AM (#38613445)
A similar warning could be enabled in GCC using the flag -Wsign-compare.
Re:OpenBSD – pinning all system calls by 1over137 on Tuesday December 12, 2023 @08:20AM (#38611723)
>I wonder how hard it would be to backport the integer behavior to C.

Clang and gcc have flags that can make integer overflow trap.

Re:OpenBSD – pinning all system calls by piss_n_chips on Tuesday December 12, 2023 @08:20AM (#38611724)
There are compiler flags that OpenBSD could be using but aren't, which would have caught this bug without needing to convert the codebase to Rust. Using -Wconversion would have warned on the mismatched signedness of the MAX macro argument (the unsigned integer, sysno) and its result (being assigned to npins, a signed integer). Alternatively, adding -fsanitize=implicit-integer-sign-change, or a UBSan flag that includes this, would detect this at runtime for the actual range of values that end up causing a change of sign.

Though, these would also be triggered by statements like:

    pins[SYS_kbind] = -1;
Due to the pins array being of unsigned int, so all this sort of code would need to be fixed too.
Re:OpenBSD – pinning all system calls by petee on Tuesday December 12, 2023 @08:11AM (#38611656)
> Stonks only go up

And the signature totally explains their childish reply, over there and here.

Re:OpenBSD – pinning all system calls by MyMonkeyBalls on Tuesday December 12, 2023 @08:41AM (#38611916)
Yeah even a child like me is better at secure programming than Theo De Raadt
Re:OpenBSD – pinning all system calls by petee on Tuesday December 12, 2023 @08:46AM (#38611965)
Then report the bug like an adult instead of acting like you found some huge published vulnerability in code that was posted for peer review. Thats why the code is there, so others can identify issues; congrats, you did.

People make mistakes in every project. So grow up

Re:OpenBSD – pinning all system calls by tiffanyh on Tuesday December 12, 2023 @11:40AM (#38614565)
Would a code analyzer have detected this bug?

(E.g. Valgrind, Flexelint, cppcheck, clang static analyzer, etc.)

If yes, then why aren't code analyzers used on all OpenBSD code submissions, given their stance on having correct code & security focused.

Re:OpenBSD – pinning all system calls by ori_b on Wednesday December 13, 2023 @12:14PM (#38630502)
No, probably not. It requires a crafted binary to be executed.
Re:OpenBSD – pinning all system calls by qzzi on Monday December 11, 2023 @10:00PM (#38608796)
I don't know how they define `MAX`, but I'm guessing it's a typical "a>b?a:b". In function `elf_read_pintable` the `npins` is defined as signed int and `sysno` as unsigned int.

So this comparison will be unsigned and will allow to set `npins` to any value, even negative:

  npins = MAX(npins, syscalls[i].sysno)
Then `SYS_kbind` seems to be a signed int. So this comparison will be signed and "fix" the negative `npins` to `SYS_kbind`:

  npins = MAX(npins, SYS_kbind)
And finally the `sysno` index might be out of bounds here:

  pins[syscalls[i].sysno] = syscalls[i].offset
But maybe I'm completely wrong, I'm not interested in researching it too much.
Re:OpenBSD – pinning all system calls by LukeShu on Monday December 11, 2023 @11:20PM (#38609435)
> I don't know how they define `MAX`, but I'm guessing it's a typical "a>b?a:b"

Indeed: https://github.com/openbsd/src/blob/master/sys/sys/param.h#L...

> Then `SYS_kbind` seems to be a signed int.

It's an untyped #define: https://github.com/openbsd/src/blob/master/sys/sys/syscall.h...

I believe your whole analysis is correct, that running an elf file with an openbsd.syscalls entry with .sysno > INT_MAX will allow an out-of-bounds write.

Re:OpenBSD – pinning all system calls by trealira on Monday December 11, 2023 @11:39PM (#38609584)
> It's an untyped #define

Pure decimal integer literals (like 86) are typed as "int" in C, rather than being typeless and triggering type inference. This is a pain when you accidentally write something like this:

  uint64_t n = 1 << 32;
On modern desktop platforms, an int is 32 bits, so 1 << 32 is 0, not 2^32, even though a 64-bit integer is wide enough to support that.

Regardless, it's not relevant here, because if an integer and an unsigned integer of the same size are compared the integer is implicitly cast to unsigned integer, and 86 is fine for both signed and unsigned integers (so "MAX(npins, SYS_kbind)" is safe).

Re:OpenBSD – pinning all system calls by trealira on Monday December 11, 2023 @11:55PM (#38609698)
> Then `SYS_kbind` seems to be a signed int. So this comparison will be signed and "fix" the negative `npins` to `SYS_kbind`:

  npins = MAX(npins, SYS_kbind)
No, the comparison is unsigned here. They're integers of the same "conversion rank", so the unsigned type wins and the signed integer is interpreted as unsigned.

https://en.cppreference.com/w/c/language/conversion

I can never remember the integer conversion rules and end up looking up this link whenever necessary.

Re:OpenBSD – pinning all system calls by qzzi on Tuesday December 12, 2023 @02:09AM (#38610529)
No.
Re:OpenBSD – pinning all system calls by piss_n_chips on Tuesday December 12, 2023 @07:42AM (#38611426)
This isn't correct, as npins is a signed int and SYS_kbind is just a macro for the integer 86 (as defined in sys/syscall.h). So it will be a signed comparison between the value of npins and 86.
Re:OpenBSD – pinning all system calls by trealira on Tuesday December 12, 2023 @08:20AM (#38611725)
My bad, I thought npins was unsigned.
Re:OpenBSD – pinning all system calls by tedunangst on Monday December 11, 2023 @10:50PM (#38609195)
That seems about right.
Re:OpenBSD – pinning all system calls by accessvector on Tuesday December 12, 2023 @06:19AM (#38610943)
Out-of-bounds heap write happens in this function:

        int
        elf_read_pintable(struct proc *p, Elf_Phdr *pp, struct vnode *vp,
            Elf_Ehdr *eh, uint **pinp)
        {
         struct pinsyscalls {
          u_int offset;
          u_int sysno;
         } *syscalls = NULL;
         int i, npins = 0, nsyscalls;
         uint *pins = NULL;
        
    [1]  nsyscalls = pp->p_filesz / sizeof(*syscalls);
         if (pp->p_filesz != nsyscalls * sizeof(*syscalls))
          goto bad;
    [2]  syscalls = malloc(pp->p_filesz, M_PINSYSCALL, M_WAITOK);
    [3]  if (elf_read_from(p, vp, pp->p_offset, syscalls,
             pp->p_filesz) != 0) {
          goto bad;
         }
        
    [4]  for (i = 0; i < nsyscalls; i++)
    [5]   npins = MAX(npins, syscalls[i].sysno);
    [6]  npins = MAX(npins, SYS_kbind);  /* XXX see ld.so/loader.c */
    [7]  npins++;
        
    [8]  pins = mallocarray(npins, sizeof(int), M_PINSYSCALL, M_WAITOK|M_ZERO);
         for (i = 0; i < nsyscalls; i++) {
    [9]   if (pins[syscalls[i].sysno])
    [10]   pins[syscalls[i].sysno] = -1; /* duplicated */
          else
    [11]   pins[syscalls[i].sysno] = syscalls[i].offset;
         }
         pins[SYS_kbind] = -1;   /* XXX see ld.so/loader.c */
        
         *pinp = pins;
         pins = NULL;
        bad:
         free(syscalls, M_PINSYSCALL, nsyscalls * sizeof(*syscalls));
         free(pins, M_PINSYSCALL, npins * sizeof(uint));
         return npins;
        }
So first of all we calculate the number of syscalls in the pin section [1], allocate some memory for it [2] and read it in [3].

At [4], we want to figure out how big to make our pin array, so we loop over all of the syscall entries and record the largest we've seen so far [5]. (Note: the use of `MAX` here is fine since `sysno` is unsigned -- see near the top of the function).

With the maximum `sysno` found, we then crucially go on to clamp the value to `SYS_kbind` [6] and +1 at [7].

This clamped maximum value is used for the array allocation at [8].

We now loop through the syscall list again, but now take the unclamped `sysno` as the index into the array to read at [9] and write at [10] and [11]. This is essentially the vulnerability right here.

Through heap grooming, there's a good chance you could arrange for a useful structure to be placed within range of the write at [11] -- and `offset` is essentially an arbitrary value you can write. So it looks like it would be relatively easy to exploit.

Re:OpenBSD – pinning all system calls by accessvector on Tuesday December 12, 2023 @09:37AM (#38612669)
Re-reading this, my analysis is slightly incorrect: the `MAX` at [5] with an unsigned arg means we can make `npins` an arbitrary `int` using the loop at [4].

Choosing to make `npins` negative using that loop means we'll end up allocating an array of 87 (`SYS_kbind + 1`) `int`s at [8] and continue with the OOB accesses described.

You'd set up your `pinsyscall` entries like this:

    struct pinsyscall entries[] = {
        { .sysno = 0x1111, .offset = 0xdeadbeef }, /* first oob write */
        { .sysno = 0x2222, .offset = 0xf000f000 }, /* second oob write */
        { .sysno = 0xffffffff } /* sets npins to 0xffffffff so we under-allocate */
    };
`npins` would be `0xffffffff` after the loop and then the `MAX` at [6] would then return `86`, since `MAX(-1, 86) == 86`.
Re:OpenBSD – pinning all system calls by actionfromafar on Tuesday December 12, 2023 @10:58AM (#38613896)
I must misunderstand something very basic about this code.

    if (pins[syscalls[i].sysno])
but pins is newly allocated and should just zero or "empty". Why dereference it right after allocation?
Re:OpenBSD – pinning all system calls by accessvector on Tuesday December 12, 2023 @11:05AM (#38614016)
Just to handle the case where the same syscall number is specified twice by the ELF header: in that case, the entry is set to -1 (presumably meaning it’s invalid).
Re:OpenBSD – pinning all system calls by actionfromafar on Tuesday December 12, 2023 @12:23PM (#38615285)
I still don't get it. Shouldn't [9] always evaluate to false, and the code be equivalent to:

    pins = mallocarray(npins, sizeof(int), M_PINSYSCALL, M_WAITOK|M_ZERO);
    for (i = 0; i < nsyscalls; i++) {
        pins[syscalls[i].sysno] = syscalls[i].offset;
    }

Edit:

Hang on - npins is already checked in the loop before, and incremented with ++

syscalls[i].sysno can't be larger than what is allocated with:

pins = mallocarray(npins, sizeof(int), M_PINSYSCALL, M_WAITOK|M_ZERO);

So I still can't find the problem

Re:OpenBSD – pinning all system calls by accessvector on Tuesday December 12, 2023 @01:40PM (#38616405)
Consider this:

    struct pinsyscall entries[] = {
        { .sysno = 1, .offset = 0x1234 },
        { .sysno = 2, .offset = 0x5678 },
        { .sysno = 1, .offset = 0x9abc }
    };
Now `nsyscalls` will be 3 and `pin` will be an array of 3 ints, initialised to `{ 0, 0, 0 }`.

When we loop through, we'll set:

    1. `pin[syscalls[0].sysno] = 0x1234` => `pin[1] = 0x1234`
    2. `pin[syscalls[1].sysno] = 0x5678` => `pin[2] = 0x5678`
Now when we come to 3, we'll find `pin[syscalls[2].sysno] != 0` since `syscalls[2].sysno == syscalls[0].sysno` - so we set `pin[1] = -1` instead of `0x9abc`.
Re:OpenBSD – pinning all system calls by actionfromafar on Tuesday December 12, 2023 @05:54PM (#38619922)
Oh, thanks, now I understand why there is an if in the for loop! But I still can't see how pin[] could be accessed out of bounds, since the array is allocated to be large enough to hold the largest value of .sysno occurring in the entries[] array.
Re:OpenBSD – pinning all system calls by accessvector on Wednesday December 13, 2023 @07:12AM (#38626235)
Right, so this is the crux of the vulnerability. Firstly, note that the `MAX` macro is defined as:

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
This is important because it doesn't cast either arg to any particular type. You can use it with `float`s, or `int`s, or `u_int`s... or a combination.

Referring back to the implementation, the first use of `MAX` (inside the `for` loop) is this:

    ...
    for (i = 0; i < nsyscalls; i++)
        npins = MAX(npins, syscalls[i].sysno);
    ...
`npins` is an `int`, but `sysno` is a `u_int`. C integer promotion rules means that we'll actually be implicitly casting `npins` to a `u_int` here; it's as if we did this:

    ...
     npins = MAX((u_int)npins, syscalls[i].sysno);
    ...
This means that `npins` can end up as any value we like -- even up to `0xffffffff`. But remember that `npins` is _actually_ a signed int, so once it comes out of `MAX`, it'll be signed again. Thus we can use this to make `npins` negative.

Once we're out of the loop, `MAX` is used again here:

    ...
    npins = MAX(npins, SYS_kbind);
    ...
Where `SYS_kbind` is just:

    ...
    #define SYS_kbind       86
    ...
Integer literals in C are signed, so now this use of `MAX` is actually dealing with two signed integers. If we used the loop to make `npins` negative (as described just before) then this line will now take 86 as the maximum of the two values.

With `npins = 86`, an array of 86+1 will be allocated, but the `syscalls[i].sysno` in the next loop could of course easily be greater than 86 -- thus leading to out-of-bounds array access.

Re:OpenBSD – pinning all system calls by actionfromafar on Wednesday December 13, 2023 @08:01AM (#38626706)
Thanks! Very clear explanation!

So then it depends on if whatever code loaded the elf section does any validation of the data it reads. I can't help but thinking the whole code could use some structs and/or "getter setters" to talk dirty objective oriented speak. It needn't be that though, it could be as low level as some macros which helps doing the right thing with signedness and such. But my main impression is that a lot of the data structure semantics is kept in the heads of programmers instead of being formalised in the code.

In C there is always the opportunity to run with scissors in the middle of the road, but you can do a lot to protect yourself too, without loosing much, if any, performance.