Instances of "unpleasantness" that would not have happened on Genode

(This will include some C code explanations so putting this in Developer's Corner as that seems like a good fit)

It’s common these days for evangelism/advocacy purposes to have a “our system would never have this defect” thread. For instances the MAID SAFE project has an ongoing thread with a title like “This would never have happened on MAID SAFE”, which gets updated whenever a famous website gets hacked, or during more eggregious censorship episodes. So I figure it would be nice to list examples of crashing bugs or security exploits that plague the IT world, and find out whether they could have occured on Genode-based operating systems (spoiler: they probably wouldn’t :wink: )

An obvious recent example would be the NULL-indirection in a CrowdStrike kernel module that crashed user-facing systems the world over.

But instead of that, today I’m going to share a bit of TTS lore which is not (well) known by the public at large.
Most non-devs (and many devs) will probably be bored to tears by the below… But for me it’s engraved in my mind, it’s written in flaming letters in the sky. So here goes.


Once upon a time (a decade+ ago), in an OS galaxy far far away, there was an Audio CD ripping application called TunePrepper. For a time it ran on an OS called BeOS, and it was a hit with users, and things were good. Radio stations need to convert their Audio CD collection to MP3s on their broadcast computers, and TP was the cat’s meow for that purpose.
But then BeOS went belly-up (or, to be more accurate, was slain, but this is not the place for revisiting the MS anti-trust trial, come on). So we had to port our full TTS software suite (TunePrepper included) to the successor of BeOS, let’s call them “Be2”. That successor improved on BeOS in several ways, but it had an even larger “kernel land” : the Be2 designers thought that e.g. the net_server component, which was in user-space, should become a kernel-space component. “For improved performance” (that was the thinking at the time). (Spoiler alert: net_server was not the culprit in the below story, I’m citing it just for illustration purposes).

Anyway back to TTS: the TunePrepper port was fairly uneventful, it went well as expected… Except the app kept crashing the Kernel, “triggering a KDL”…
“KDL” stands for “Kernel Debugging Land”. The correct terminology would thus be something like “the app kept triggering kernel segfaults which would display the white-screen-of-death Kernel Debugging Land kernel debugger”. But that’s a mouthful, so people just say “the app KDL’ed”, that gets the message across. Anyway. Carrying on.

The KDLs were not entirely reproducible, some CDs would rip ok, you’d get a long string of successful rips, then at some point a rip would trigger a KDL right when the CD would get inserted into the CD tray. Some others would trigger a KDL when selecting another window after being done ripping… There was no definite pattern. The only definite common denominator was TunePrepper (which allowed for eventual resolution of the bug, though with lots of efforts and elapsed months… unlike the rest of our problems which were not so “easy” to fix… but I’m getting ahead of myself again).

So what did the “white screen of death” say, what was the crash message exactly ?

Again, there was no definite pattern in the message, just like there wasn’t in the behavior/reproducibility. The KDLs would sometimes be related to TunePrepper, sometime to random kernel modules. It would crash on corrupt variables in firewire (hint: we didn’t use firewire in any way, shape or form), or in VFS, in the HD Audio driver, or in the network driver, that is to say, in completely random stuff.

To put things in context : TP was a CD ripper. The way it worked was, the user would feed some Audio CDs to the CD-ROM tray, TP would automatically mount them and “rip” them.
During the mounting, the system would automatically do a CDDB lookup to find out the artist and album name of the CD and its tracks… “CDDB” stands for “audio CD database” – basically it has an API where you feed it with the “digital signature” of an Audio CD, and it returns the title of the album and of all its tracks. So after using that server API, TP would rename the mounted device : it would change the original generic name (something like “Audio CD”) to its looked-up name (for instance: “Ark - Burn the Sun” (spoiler alert: that latter name is a few bytes longer than the original name – see where this is going ?)

So TP kept KDL’ing the system. So we filed tickets, lots of them, with photos of the KDL messages and backtrace. Sometimes we’d try to post to an existing ticket, but would get a response like “this KDL message looks nothing like the one you opened the ticket with, you should open a new ticket”.

Days turned into weeks. Weeks turned into months. I was very much looking at solving this, not just for the sake of having software that runs and can be sold, but also for the sake of “lessons leaned” : I was looking forward to finding the bug, drawing conclusions so that it would never occur again, and engraving those in a cast-iron panel hung on the wall of my office ^^.

One episode I remember was : I’m looking closely at the KDL message, which mentions (as often) a corrupt 32-bit register, and I start thinking “this 32 bit word looks like a series of 4 ASCII bytes”. Converting the hex to ASCII, I find something like “e Su” and I realize “oooh, that could be a part of the string 'Ark - Burn the Sun” being used for a buffer overflow ?". Nothing definite, but a hint. At last! After months of no hints, and given how desperate I was, I started hanging on it for dear life. So I start going through my code : the album metadata gets fed to various BString classes, BStringView, BMenuItem… Those are used throughout the system, that can’t be the culprit ? Even if by miracle one of them had a way to trigger a kernel bug, it would manifest its way throughout the OS, not just in TunePrepper ? I even try to to a “synthethic recreation” of the bug, right in my userland code (if only I had known…), I hardcode the name to “XXXX…XXXXXX” (a series of 64 bytes as I recall). That led me nowhere, I gave up. Not trying to find excuses for myself, but after a long time churning on it, that lead started to look weak, and since it occured in one of the later tickets, it didn’t occur to me at that time to think of the real cause (which will be revealed below).

Meanwhile the tickets kept accumulating (this whole thing went on for months), the screenshots, the back-and-forth, the Be2 team getting tired of hearing from us, etc.

What to do ? What to do ?

We finally paid a little ‘bounty’ to a member of the Be2 team, the one best suited for bug hunting, known for creating the Be2 guarded heap, the leak analyzer and other tools – really rigorous guy, great value to the team and an ass-kicker whenever he set out to fix a bug (but unfortunately that was infrequent, as he had little time to dedicate to Be2 development, it might have changed history otherwise). I remember we agreed on something like, “find the bug, receive $500”. Worth the price considering the effort it took him, and all the bugs he had fixed for us and for the community previously without receiving any compensation, it was also a way to catchup on thanking him.

So he tries out different strategies and after a while, he decides to compiles the OS with guarded heap, and runs its in Qemu, emulating an Audio CD, running TunePrepper. The thing takes forever to boot up and to run the app, as the guarded heap is terribly slow and hugely memory-hungry compared to a normal system with a normal memory heap. Plus he has to wade through tons of false positives (i.e. guarded heap “hits” that were irrelevant to our bug), because, well, that’s Be2 we’re talking about.

In the end he does trigger a guarded heap “hit” that also happens to occur right before a KDL, when inserting the (virtual/fake) Audio CD in the Qemu emulator. Yay! There we go. The moment of truth. The hit is about a buffer overrun in… (drum rolls)… rootfs. The file system that gets populated with hard drives, CDs and the like.

The offending code looked something like this:

if (strlen(newName) > strlen(fs.name))
  // no need to re-allocate, keep the old buffer
  strcpy (fs.name, newName)
else
{
  free (fs.name);
  fs.name = strdup (newName)
}

Did you spot the mistake ? Yup. Exactly. The “if” logic is reversed.

And this, ladies and gents, is how you create a semi-subtle kernel crash, that will clobber adjacent memory that might or might not be used by another kernel component, which might or might not detect it and crash immediately, but will highly likely NOT hint at the real culprit (rootfs), it will crash/point the finger at another component instead.

Why didn’t that bug get found sooner, by someone else? People don’t rename their root volumes (hard drive partitions) all that often. And probably not with names as long as “Beatles - Yellow Submarine” that are more likely to clobber important memory. Hypothetically speaking, with a small user base like Be2’s, maybe someone had filed one ticket about it in the previous years. The ticket probably had a KDL message pointing the finger at another component, and thus remained unresolved, or even dismissed as “just a fluke”. Heck, we filed tons of tickets and screenshots, because we had a somewhat reproducible case, since ripping dozens of CDs one after the other resulted in dozens of rootfs renaming, yet no-one found the cause… So how could we lay the blame on an hypothetical prior ticket not being resolved?

Anyway. Bug found. We have to pay the bounty. I remember talking to Dane (the head honcho of TTS ^^) and he was incensed, in two ways :grin: Like “how can a single line of code bring down the whole OS ??” and “I’m not paying up that much for just a single line of fixed code !”. I seem to remember we shared the price eventually, but I almost had to pay out of my own pocket, he was so p-o’ed ^^. I can put myself in his shoes: non-developers have no idea how brittle a big-kernel is, that a single NULL indirection or buffer overrun can make the whole thing a flaming wreck.

So what now. We were in a good place afterwards, right ? After finally finding this if/else logic reversal, the fix was pushed into a commit, a new nightly build was made, we installed the nightly and verified that this KDL no longer occured. Problem solved, right ? We no longer KDL with a buffer overflow when inserting an AudioCD, we’re good to go ?

Nope. There soon was another problem. Then another. Then another. Then another.

We threw in the towel : let’s stop sales of TunePrepper completely, and instead focus on the rest of our software suite.

But then the “rest” had a problem too. Then another. Then… Ok I’ll stop here.


So what’s the “lesson learned” bottom line here, about this particular kernel and how it’s developed ?

  • are they lazy-bones coders who do not measure up ? Of course not, they re-created a whole Operating System with little prior experience of OS development, in the space of a few years, without (originally) being paid anything. That’s pure heroics right there.
  • maybe Donald Knuth was right, “premature optimization is the root of all evil”, especially if (speculating a bit, here) it’s the kind of if-else optimization done late at night running on (coffee) fumes ? Maybe.
  • maybe it’s our own fault at TTS, we didn’t file enough tickets, and they would have found the bug faster, had we filed more tickets ? HA ! Good one. You have me up in stitches here.
  • maybe there exists a magic trick that can make kernel coding easy and we are all too stupid to see it ? Of course not. The only way to make big kernels stable is to be Google with its billions $$$, or have a huge user base and dev base that “work harder, not smarter”. And even if you “check all the marks” you might still get a “CrowdStrike”, despite all your billion dollars and your hardworking, blood-injected-eyes droids.

The answer is of course, none of the above really matters, due to the original sin. Don’t put all your eggs in the “kernel” basket and expect to avoid Amiga-style “Guru Meditations”.

Bottom line – none of that would have happened on Genode-based operating systems.

2 Likes

A “user” level server might cause a lot of headache since its dependencies might stop the entire system. It is a better way compared to big kernels, but there will always be bugs. I would like to share one thing… In my RISCOS/Genode demo I hijacked the system. At the time I created the demo I needed a way to run privileged code. I had no clue where to look: so I did SVC #555, naturally that gave me direct connection to the systemcall thing. Of cource in a demo it doesn’t matter. I implemented my syscall and was ( is ) happy. But the situation is kind of unsafe. If I know of a call , I can access it at any time. It can definitely cause problems , and together with a bug it can easily bring everything down. :smiley:

1 Like

Oh don’t get me wrong, there’s plenty that can go wrong, I know for a fact ^^

But the kind of “wrong” that really sends me in a frenzy is the “false flag” (so to speak) phenomenon, when component X corrupts the memory of component IBS (innocent bystander)… So that the report you get in LOG is “IBS seg fault !”. This I know cannot and will never happen. If I see a seg-fault in a component, I know it’s the culprit, it’s the one that cr@pped the bed. Nobody else. No matter the excuse, “but but but… another component sent me a mean RPC request with arguments I didn’t expect !”. Yeah well, deal with it, Mr Component. You’re supposed to be a good, well-behaved, solid component. I won’t take that attitude from you, Mister Component !

Seriously, just knowing that, help hugely enormously immensely.

But yeah, if something like nitpicker or event_filter (which is fairly critical since it filters both ps2 and usb input, so can ‘paralyze’ both at the same time if it ever buys the farm) ever crashes, you’d completely lose interaction with the system. If you have an SSH connection you can at least interact with the system that way, otherwise it’s just hard-reboot time.

Which is why the Genode team is so maniacal in their keeping their source line count (for TBC, trusted code base, critical components like nitpicker) low, running automated nightly tests, adding paranoid design pattern after paranoid design pattern, and all that rigorous jazz ^^

As to SVC 555, no idea what that means ^^. That would be for the Genode team to analyse and determine if it’s a vulnerability that needs addressing ? (does Genode really have “syscalls” like other OSes, I thought RPC calls somewhat replaced or displaced that concept)

1 Like

The lowest layer have a vector that is like syscals. I don’t remember right now if they are called that now. SVC is an assembler instruction that puts the system in a cpu exception state. In my case building Genode for custom systems it is safe. But I don’t know if it is possible for a application in sculpt doing it.

This is the official way of entering the kernel, nothing spooky. On base-hw, the kernel interface is split between system calls only allowed by the core component (core-private) and calls allowed by any component (public kernel interface). Should any system call have an unexpected global effect on the system, this would be a kernel bug.

does Genode really have “syscalls” like other OSes, I thought RPC calls somewhat replaced or displaced that concept

There always must be a way for a userland application to interact with the kernel. For example, when issuing an RPC, the application needs to tell the kernel about this intention. This is what a system call is for. It gives the application a way to hand over control to the kernel, asking the kernel for a favor.

On traditional kernels, there is a huge API surface covered by system calls. In contrast, a microkernel interface is boiled down to the absolute necessities needed to run programs and to let those programs nicely interplay. Still, in both cases, a kernel-entry mechanism is needed. On ARM, this is the SVC instruction.

1 Like

Thanks Cedrik for sharing this story.

Besides the technical argument, it also illustrates quite well a common pattern of friction when commercial ambitions meet open-source projects, often yielding frustration on both sides.

From the perspective of a commercial product vendor basing the product on an open-source project (like in your case using Haiku as a drop-in-replacement for BeOS), it was probably unrealistic/unfair to look at Haiku as a product. You got it for free, with no guarantee for its fitness. There is no OS-vendor-customer relationship that would put you in a strong-enough position to make demands. Even the request to fix a brutal bug comes down to seeking the goodwill of the fellow developers. You were still confident enough, took the risk, and got disappointed. I can very much feel you pain. But there is also pain on the side of the developers who put so much of their lifetime and energy into their project, and getting complaints as reward. I can very much feel their pain as well. Nobody had any bad intention, there was no wrong-doing, or misconduct. Still, the interplay has hurt both parties. Once such friction arises, emotion kicks in, which often makes it worse.

I think the best way to avoid such situations is crystal-clear communication up front. Your expectation of using Haiku as a drop-in replacement of the commercial product (BeOS) was on too slippery a ground at that time. The incentives behind a product development and a volunteering project are too different to ignore. A product must work. If it fails, the vendor is liable. So the vendor puts much effort into keeping those risks at bay to protect its business. On the other hand, a volunteering project should satisfy the goals of the volunteers, be it having fun, self-expression, benevolence, or enjoying the sense of belonging, or others.

Now coming back to the topic of the issue, I hope that a component-based OS like Genode can be a fertile ground to reconcile both development motives by having clear component boundaries. It goes without saying the core of the system must be built with the rigidity of a high-quality product. But the components atop can be created out of motives of self-expression or fun without putting the whole construction at risk. The boundaries between components of different sort and different origin cannot be more clear. Sculpt OS reinforces this clarity at every step, e.g., by letting the user select the depot of a specific author when adding a component. Maybe I’m fetching too far but I’d love to claim that the “unpleasentness” of the social fallout of your story may not have happened with a component-based system architecture to begin with. Well, let’s cross fingers and see how this claim will age. :slight_smile:

1 Like

Well said… As I added elsewhere, the “lesson learned” is not that anybody did anything wrong per se. That’s just the way it is and the way history unrolled.

Agreed with everything… Hopefully the Be2 devs will read it too and feel a little better and comforted, if they felt bad (that was not my intent). Since you can ‘put yourself in their shoes’ as a fellow OS developer, your words should carry weight with them.

So my story is just a sad story, in a way. Still, if you read between the lines, there is a “silver lining”, something can be salvaged from “Be2”, such that it becomes a win-win situation instead of a lose-lose situation, though I might have been too subtle in introducing that silver lining :wink: You make it more explicit than I did, and I’m grateful for that.