Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login
Rewrite Linux Kernel in Rust? (dominuscarnufex.github.io) similar stories update story
200.0 points by z3phyr | karma 2637 | avg karma 2.99 2017-06-04 01:21:22+00:00 | hide | past | favorite | 133 comments



view as:

Let's see what Linus thinks about this.

Probably the same as the author of curl. Something along the lines of "Yes, sure, our project is open source so fork it and go ahead".

"And don't call it Linux because I own the trademark".

I wonder what makes AT&T assembly syntax "objectively crap".

You need to explicitly specify what the parameter sizes and types are. Plus, the parameters are backwards compared to Intel syntax:

  Intel: mov eax, 5
  AT&T: movl $5, %eax
In basically every C-like programming language, you assign 5 to eax with `eax=5` not `5=eax`. I can also tell that I'm working with the `long` variant of `mov` just by looking at the types involved.

Intel's syntax for memory access seems more natural. If I don't want to add `ebx` to the address, I can leave it out; I can't in AT&T; I have to have this seemingly random comma. Plus, what's with the format?

  Intel: mov eax, [ds:eax*4+16]
  AT&T: movl ds:16(,%eax,4)

Interestingly, in the language you actually wrote that complaint in:

> you assign 5 to eax

AT&T speaks English, basically. We move A to B. Destination goes last.

Both seem sane enough. And in fact both get really balled up when you have three and four-argument instructions.


I'll grant that, but it's a programming language, so I kind of expect it to follow the order assignment does when I write in C++, C#, etc.

AT&T assembly style significantly predates C++ (and C# by like two decades), and co-evolved with C. And strictly this isn't an assignment, anyway. Assignment is an abstract operation in a programming language. This is a machine instruction[1] for a physical machine, which does exactly one thing.

[1] Pause while the peanut gallery points out that in fact there are like ninety six different instructions in x86 called some variant of "MOV". Yeah, yeah, we know.


What.

When did `mov` get 96 different instructions?


I think it's just a bit of hyperbole. I don't know enough of this to know whether http://c9x.me/x86/html/file_module_x86_id_176.html is complete, but it gives you a sense of it all. (also https://www.cl.cam.ac.uk/~sd601/papers/mov.pdf if you haven't seen it)

Huh, interesting.

Apparently there are 35 rows in the table at http://www.felixcloutier.com/x86/MOV.html (according to Chrome and `$0.childElementCount`). I only discovered gcc.godbolt.org links to documentation via context menu just the other day!

Regarding the PDF... ouch. Wonder if any executable obfuscators have tried that approach :P (and I also wonder what sort of overhead it has.)


> I kind of expect it to follow the order assignment does when I write in C++, C#, etc.

There is no special precedence that should be applied to C. Similarity to C only imparts benefits to those that are fimilar with C-like languages or variants that follow that structure. That ends up being a lot of people because of how popular C and variants have been, but that's doesn't inherently make it better.

It's sort of a pet peeve of mine when people point out differences from C as problems without any objective facts to back up whether it actually is more or less useful in some aspects. If we never stood up to that type of thinking, we'd all by much poorer in the programming language department.


This does raise the question: which languages other than AT&T assembly put the value first?

AT&T syntax puts the operator first, not the value. AT&T is Verb Subject Object. Intel is alien Verb Object Subject.

Many kinds of S expressions result in similar code. That would mean Lisp derivatives.


> AT&T syntax puts the operator first, not the value.

If "operator" has any meaning in assembly language, it would have to refer to the opcode. Both variants have the opcode (mov/movl) first.

> AT&T is Verb Subject Object. Intel is alien Verb Object Subject.

Both "move 5 into EAX" and "move into EAX the value 5" are imperatives. Neither of them has a subject, or more precisely, the subject is omitted (https://en.wikipedia.org/wiki/Imperative_mood). In both of these, 5 is an object. "into EAX" is an adverbial phrase or something.

It's true that one sounds more alien than the other when forcibly translated to English. Then again, assembly language is not English, so that's neither here nor there.


Sloppy of me, but I meant value before the variable/register.

Makes you wonder why not write it as e.g. "eax mov 5" or even "eax = 5".

I've seen assembly languages that do separate target and source operands with an = sign. So you would write that something like:

    mov eax = 5
and use a similar style for other things, like:

    add eax = ebx, ecx
This is very nice and clear. But it only really works for three-address assembly languages, i.e., where the target register may be different from the sources. In classical x86, the target of an add is identical to one of the operands. So you would need something like

    add eax += ebx
or

    add eax = eax, ebx
where you must duplicate a register. Presumably when these assembly languages were developed 40 years ago, this was perceived as too much syntax.

> nice and clear

Looks absolutely horrible to me, why not simply

    eax = ebx + ecx

?

+1. Make it look more like a severely restricted version of C.

I guess because there's no simple infix operator for every operation implemented by the machine. Also, the addition may be floating-point or 32, 16, or 8 bit, signed or unsigned, possibly vectorized. An opcode is a good place to express these things.

Opcodes are fine, no one is saying everything has to be expressed as an operator. I'd like to see more of a C/C++ intrinsics syntax, but with total control over the order of instructions and register allocation. Basically same power as assembly, syntax close to intrinsics, but you tell it which registers to use, and there's one instruction per line, and they're executed exactly as you write them, with no creativity on the part of the compiler.

Right, but the mnemonic reads better in AT&T then "move 5 to register eax" and Intel seems backwards.

It's what you get used to. I'm used to x86 being "dst,src". It never bothered me that on the Motorola 68000 it went "src,dst" as, it was what it was. Intel defined it one way, Motorola another way [1].

My gripe with AT&T style (and it's called AT&T because K&R wrote their assembler that way) is that it uses sigils to mark registers (something I've never seen any other assembler do) and specifically broke with the manufacturer's mnemonics. Yes, I can see why they did it that way (a consistent format for assembly across multiple architectures) but I don't necessarily agree with it (Go's assembler has gone father by not even bothering with the register names the manufacturer uses).

Almost forgot---another annoying aspect of AT&T is the use of "$" to designate an immediate value. Every assembler I used (prior to my exposure to Unix) used "#" for immediate value, and "$" for hexadecimal. But noooo, AT&T had to use "$" for immediate, and "0x" to designate hexadecimal and thus, I hit a brick wall each time I read code in AT&T format.

[1] Also interesting to note---Intel is little endian, Motorola is bigendian.


I learned on Intel and that's the only I can use. Just pointing out that reading the expression makes more sense in AT&T style so might be why they did it that way.

And always wondered why the need for % in AT&T for registers. It seems like there would no ambiguity based on instruction.


Why would you expect assignment order to be similar? Nothing else about c syntax is shared.

If anything, at&t follows English: move literal 5 into register eax.


Wow. Literally a 40 year old thread!

well not quite, but slightly more than 30 years - you point is right - those who don't read history are doomed to repeat it

I thought gas did both...

It does, now, but it used to not.

All the intel docs use intel syntax, and for some instructions converting between the 2 syntax forms isn't immediately obvious.

I wish there was a version of Intel's docs with AT&T syntax.

The best set of arguments I've seen against it: http://x86asm.net/articles/what-i-dislike-about-gas/

Pretty cool. I decided to take a similar path in trying to improve/ensafen parts of the BEAM VM by finding small chunks I could rewrite in Rust from C.

Most of the headache was build toolchain integration stuff. I did manage to get a few simple things slotted in that appear to work transparently, which made me hopeful for future more complicated things to play with like new process mailbox implementations, etc.

Anyway, great write up and a cool project!


That sounds like another nice idea.

What happened to your project?


Ongoing, but BEAM is no longer part of my day job (though Rust is), so it's slower going and only moves occasionally forward between the hours of 12am and 3am when the rest of my family is asleep.

Thank you.

I was just reading your comments and found this one:

>Erlang: the general disinterest of Ericsson and the hapless stewardship? of Erlang Solutions.

Is that related to the reason that BEAM is no longer part of your day job?

The reason I ask is that I am thinking of starting a new project with BEAM, maybe some Rust or C++ and wondering whether Elixir has taken over from Erlang due to the disinterest of Ericsson.


Nope, not the reason. My "go-to" language when prototyping most things is still Erlang.

Erlang is by far my favorite language and environment to use, and for a whole slew of things I'd still grab it for production projects/products.

I moved into the world of autonomous vehicle runtime software, and tools closer to the metal with deterministic timing characteristics are a requirement.


Thank you for the useful clarification.

From what you write, it sounds like in production that, if one is attracted by the BEAM, there is less risk in choosing Erlang than Elixir.

One thing that concerns me for production use is the combination of the Erlang license and the possibility of Ericsson "doing a Sun" by selling the technology to Oracle, which might lead to an Android like situation.


Well... Elixir has a thriving community around it right now. Erlang has a lot of really mature libraries and frameworks.

It's trivial to include Erlang in an Elixir project, it's slightly less simple to go the other way, but it's still doable. In either case I don't think you have much in the way of risk regarding Ericsson pulling a Sun. When I say Ericsson is "disinterested" I just mean they're not inclined to promote or progress the language with the kind of pace and/or hype required to keep a thriving and active Silicon Valley shaped community going.

It's a tool that works well at exactly what it was designed for. There's no driving force trying to move it further to take over the general purpose programming world like Java had or Go has.

Anyway, we're epically off topic :-) I think my email is in my HN profile, so happy to take this offline.


True - we are way off topic!

Sorry and thank you again - was very interested in your experience.


What is it about Rust that helps with the deterministic timing thing you mentioned? Just not having a GC? Do you still use C or C++?

No GC, optional runtime (no_std), etc.

Yep, still use C and C++.


Don't suppose your work is online? I'd be interested to read your patchsets.

Sitting in my FreeNAS Gogs instance, but I don't suppose there's any harm putting it up on GitHub when I'm back from traveling at the end of the week.

OT: I really like the word "ensafen", and I'm totally going to borrow it for future use

It's a perfectly cromulent word, and embiggens every sentence and phrase that includes it.

Embiggens? I think the point of the word is to debigulate some sentences. I would say that it embetters phrases that include it; maybe that's what you meant.

Double plus good point.

Do it in .net That would be useful

Without making any assertions as to the benefits or problems of integrating Rust into the Linux kernel to replace components, I just want to point out it's really cool that you can and that someone has gone through the trouble of documenting how. That's just awesome.

Discussion about whether this is a good idea or not, and the problems of doing so, can now commence in earnest, and without the pesky problem of it being mostly theoretical.


Yeah it's always great to see concepts implemented and working. My favorite similar project I would use as an argument to bring Nim into a legacy C codebase is https://github.com/ckkashyap/nimxv6 which started off by taking the xv6 kernel and replacing uart.c with uart.nim. Every step of the way you can test your translation was correct, and once you have Nim in the build process you can start using it for new things too.

Do you get anything with him? I was under the impression dereference safety wasn't a priority.

Debatedly, you get a better stdlib and some usability features.

People tell me it's as readable as Python, has macros, compiles to efficient C (among others), and is easier to learn than Rust esp since it's GC'd. These are good reasons to use it for many apps. I put it in Go's category mostly. However, I think affine types (Rust's advantage) could be added to it or a safety-critical subset.

Nim or Rust might be appropriate depending on the project. To me the idea of replacing C with either of them comes down to a value-sell of how much you value Rust's safety guarantees and whether you're willing to make the investment of learning to use it or if you're just going to wrap everything in unsafe{}. Considering you're talking to C programmers, I don't think they value that safety very much. If they did, at least compared to other values, they'd have moved to a GC language like Java long ago, or at least a C++-subset-with-rules. So Nim has a lot of nice features that they might like and expect from modern higher level languages, a lot you can use even if you alloc() everywhere and avoid the GC, which you can do because it has an optional and implementation-swappable GC. And it's not like Nim is just as unsafe as C if you don't use the GC, the compiler stops you from shooting yourself in the foot for a lot of things plus there are some good (tunable) defaults on runtime behavior. Finally the ramp-up time to get productive in Nim, and ongoing development effort, is probably less than Rust, which will help the sell too.

pardon my ignorance, but wasn't "safe-and-performant" impossible before rust?

Isn't choosing a GC-language some trading off performance for safety?


There were other attempts to bridge safety and performance.

C++11 already gets? pretty close to that, but Microsoft's M# was used to write a research OS and is probably the closest.


C++, though, only works if you follow a bunch of coding guidelines (that I can tell you for free the last C++ team I worked on didn't) and don't interface with any library APIs that don't follow those guidelines.

Don't get me wrong, C++ is coming on by leaps and bounds, but if you want to get to safe and performant without GC, it's not the place you should be starting. (Equally, I'm not saying Rust solves all the world's problems. Just that it's a better place to start.)


I think it was "safe and performant without a GC" because both Nim and D have C++ level performance while being memory safe as any GCd language is.

You forgot about pony, which is memory safe plus concurrency safe plus faster than C/C++. Nim, D, Rust won't offer that. M# does because it followed the same principles.

It's a common misconception that GC languages must be slower than languages without one. Where that misconception probably comes from is that it's pretty easy to write performant code in C, and hard to write terribly slow code, whereas in languages with GC in some instances you can do as good or better but it's often harder. These days if you're comparing Java and C++, it's not unusual for the Java code to be faster. The bigger tradeoff for GC is more in the loss of determinism, some applications (like games) can't tolerate a random stop-the-world collection for n ms. This can be mitigated by pauseless/stoppable/real-time-guarantees GC implementations though so if your language lets you swap the GC you have options around this. (For something like a game, naive malloc/free aren't enough either. You're going to have to manage your memory intelligently regardless of if you have a GC or not.)

As for Nim all I can say is run some benchmarks on C, Rust, and Nim. Compare the ease of implementation. Compare the relative safeties beyond memory ownership safety. Nim does very well.


You mentioned the determinism tradeoff, which is one the gamedev community talks about endlessly. I'm no low-level expert, but my understanding is that determinism isn't all you lose.

My understanding is that the big tradeoff is against RAM usage. You can have relatively-low-RAM-usage JVM programs, and you can have fast JVM programs, but you usually cannot have both. Keeping the total cost of GC low is done by amortizing the cost over more run-time, which means running less GC often, which means accumulating more garbage between runs. I mean, compared to the insanity that is an Electron app or something, it's no big deal, but this means that the pre-Rust tradeoff is something like "Memory safety, small memory footprint, fast running time: choose two". (I want to emphasize that I'm repeating hearsay, not reporting from my own knowledge.)

Also, it's probably a minor point, but JVM startup time is terrible compared to a native binary. Many many programs don't care about this, but some do.

Code size (not counting the JVM) might well be improved, but if this is the only JVM code on this machine and you need to pay that cost, that's a tradeoff too.


You're right that total memory can be a tradeoff since any GC will necessarily have some space overhead, the question is how much, and whether you can accept some time tradeoff in exchange for less space. So again it all really depends on the GC implementation (or implementations, e.g. standard Java can swap implementations too). Remember that Java was originally designed to run on embedded systems, Lisp has been used on very tiny hardware, and GC theory has been developing from at least the 60s. Memory is plentiful compared to those days.

My go-to reference when getting into GCs is http://gchandbook.org/ and it covers all this in the beginning. One old study tried a particular GC algorithm with Java and some various program benchmarks and found to match perfect manual allocation time (which will be better than in practice) you'll need about 5x the minimum memory, 3x gives you 17% overhead. I think anywhere from 1.5x to 3x is normal in practice since it depends on the application itself. There are tradeoffs everywhere and it's great Rust offers another one.

I thought JVM startup time was a dead horse. :) Sure native beats it, so does Python, but not by much these days.


I ended up doing that for the netcode.io port of Rust and it worked out really well. Both in stubbing out implementations and in calling C API from Rust until I had 100% Rust implementation.

Progressively writing parts of the kernel in Nim sounds more practical.

Perhaps Nim can be used to write kernel modules already.


> Yeah it's always great to see concepts implemented and working.

I agree. It's easy to criticize ideas that may seem futile at first, but there is nothing like a working implementation to take the wind out of the critics' proverbial sail.


"I just want to point out it's really cool that you can and that someone has gone through the trouble of documenting how."

It's a very detailed guide of how key components were incrementally improved in some way with actual source. That's almost always a good thing since it ranges from beneficial for intended goal to helping people learn things when it wasn't.


The author said:

Thank you very much for your appreciation. In my opinion, such a toy projet would not be of much use if I didn’t document it so others can learn from it or improve on it. :-)


This is an interesting idea, but probably won't work out because Linus (and other developers) may not want to transition away from the C language (see Linus's comments about not using C++.) I think it would be excellent to see Rust being used, but the implementation of replacing C with Rust could be very complicated, and Rust's benefits may not be directly visible when having to port/rewrite a lot of the components.

I mean, the advantages of C++ are complexity management and the downside is its abstractness which is what Linus' argument was about (It is a behemoth compared to C therefore easy to make mistakes).

Rust might prove to be a possible candidate because it can help with safety. This is a different rationale than what the argument for C++ was about, so we cannot say.

The bigger problem I see is how premature the Rust eco-system and understanding is. It needs to go quite a long way people people start believing in it and before the language is well understood by more than a niche group of people who learn it just out of interest.


Rust doesn't have downsides of C++ by design.

Yet. I like rust. It has a deep, thoughtful approach to many problems. But, C++ is pretty long in the tooth. Of course it's weird and inconsistent. Give rust 30 years to really entrench itself as a systems language. It'll be wierd also.

And GNU Herd weren't on board with creating a monolithic kernel.

So what? It got built.


you mean GNU HURD?

Yeah. Dang English phonetics.

If there were a fork and the fork were to gain momentum it might be not be up to Linus.

A project like this might attract new developers because it might be less intimidating than joining the Linux kernel proper. There would also be the attraction of learning Rust in a very real world context.

edit:s/in/it


This is a good argument if a massive amount of effort was piled into this with lots of refactoring on top of it. Meanwhile, it doesn't have a snowball's chance in hell of accomplishing anything.

The key for such a project being successful would be to set expectations low in defining what success was. Something like "a proof of concept of an experiment to rewrite small amounts of the Linux kernel in Rust".

One would fork Linux on GitHub, name it something other than Linux, and just have the existing branches track Linux.

Linus has stated that he isn't particularly interested in security bugs. Other people take a different view. Eg. [1], [2]

Another incremental step to improved security in Linux after a Rust fork might be to take the lessons learned to create a conservative set of Rust inspired extensions to C without all the hoariness of C++.

[1] https://news.ycombinator.com/item?id=2539839

[2] https://www.schneier.com/blog/archives/2015/11/linus_torvald...

edit 1: removed apparently dead link to original source http://lkml.org/lkml/2008/7/15/296

edit 2: clarify that one wouldn't use the name Linux for the fork


"One would fork Linux on GitHub, name it something other than Linux, and just have the existing branches track Linux"

That's already hard given how many commits go into Linux. One must always remember it's not just some volunteer project: most of the commits are by paid programmers working at companies. That's where the labor comes from. You'd need a lot of volunteers to keep up with the paid programmers. Even Linux itself couldn't achieve that. Hence, all the paid programmers working on it.

"might be to take the lessons learned to create a conservative set of Rust inspired extensions to C without all the hoariness of C++."

Now you're thinking on the right track. That sort of thing has been done multiple times at the compiler level where they automagically make C safe with a performance hit. Several teams did it at CPU level. These projects have been used on FreeBSD mostly but also Linux. The strongest one so far is CheriBSD on CHERI processor.

Alternatively, for a safer C that you're manually using, what you describe has already been designed and actually inspired Rust's safety model:

https://en.wikipedia.org/wiki/Cyclone_(programming_language)

It could be revived and improved if people wanted. It got nowhere with C programmers, though, in the past.


So we agree about something at least ;-)

Slight tangent: I started my programming career in 1989.

The product was Parasolid and the language was AGA - a C inspired language that preceded C++.

I vaguely remember the following extensions:

* built in logging with Undo

* overloaded operators leading to vector and interval types that worked better than C++

* a proper module system


Interesting. Never heard of it. The main safe language from that time was Ada. It's been updated regularly. The neat thing about it is how it was systematically designed to mitigate errors in about every operation.

http://www.adacore.com/knowledge/technical-papers/safe-and-s...

There's also a variant called SPARK that can automatically prove the absence of errors like buffer overflow in a way that would normally take a theorem prover.

https://en.wikipedia.org/wiki/SPARK_(programming_language)


>Interesting. Never heard of it.

You wouldn't of. It was developed internally to write Parasolid. Probably they still use it - several of the original devs are still there 30 years later.


IBM did that with PL/S. It was a system version of PL/I with no runtime and ability to decide how a function was compiled via annotations in that function. Kept getting updated but no public release. Here's a few more that you might find interesting for general usage that were proprietary or started internal:

http://www.semdesigns.com/Products/Parlanse/index.html

https://web.archive.org/web/20130303055647/http://www.founda...

https://en.wikipedia.org/wiki/Pike_(programming_language)

https://en.wikipedia.org/wiki/Roxen_(web_server)

Parlanse is used in Semantic Designs' powerful toolkit. Flow was used to make a Spanner competitor, FoundationDB, that was good enough in some way for Apple to buy & pull off the market. Pike was used to build Roxen server that had significant advantages in its time period. A number of companies also used LISP and REBOL for their advantages with at least one asking LispWorks to do a real-time implementation for telecom or something. So, usually it pays to go with a common, often-crappy language with its ecosystem. However, doing one's own language with competitive edge does pay off at times. I think these days one can just do a DSL in Racket or Haskell on top of main language to negate benefits of totally rolling own stuff. People got far with it, though. Galois is on a roll with that in high-assurance systems with this as an example:

https://ivorylang.org/ivory-introduction.html

So, just some fun examples of homebrew for you in return. :)


>It could be revived and improved if people wanted. It got nowhere with C programmers, though, in the past.

I guess the market will decide.

Ransomware will explode in the next few years.

If a big enough exploit gets pinned on Linux then sentiment could change and there could be a well-funded hard fork.

edit: saw this quoted on Twitter today (but can't recall where):

“Only a crisis - actual or perceived - produces real change. When that crisis occurs, the actions that are taken depend on the ideas that are lying around. That, I believe, is our basic function: to develop alternatives to existing policies, to keep them alive and available until the politically impossible becomes the politically inevitable.”

Milton Freidman

http://www.goodreads.com/quotes/110844-only-a-crisis---actua...


> Meanwhile, it doesn't have a snowball's chance in hell of accomplishing anything.

That's only true if you believe that the only possible accomplishment is to become the #1 kernel in the world.

Meanwhile, it's a very interesting project and it's a clear proof of technical proficiency. There are not many people in the world who can claim that they've written a kernel, let alone one in a cutting-edge programming language like Rust. I would love to have something like that on CV. Meanwhile, criticizing other people's projects does nothing to advance your career.


"Meanwhile, it's a very interesting project"

I was responding to a comment about doing the whole Linux kernel, not the original project. The original project makes sense. You gave another, good reason for that.

"Meanwhile, criticizing other people's projects does nothing to advance your career."

Professors grading things, peer review of research, managers reviewing performance, consumer organizations reviewing products... there's a lot of reasons that statement is wrong. I try to critique with an alternative solution presented and citations for both statements where possible. In the Linux case, I've often recommended tech at hardware and compiler level that automatically makes C code safe some of which have been used on FreeBSD and Linux. Alternatively, running it as a VM on top of a secure microkernel with security-critical apps running outside the VM.


"A project like this might attract new developers because it might be less intimidating than joining the Linux kernel proper."

Quite possible, but for the new project to be successful, existing _users_ will have to see clear benefits. The only way I can see those "new developers, using Rust" make a better product than "current developers with sometimes decades of experience, using C" if those experienced developers currently create lots of ownership related bugs.

IMO, _any_ argument claiming the way forward is a (partial) rewrite in Rust should start with some estimate of the number of ownership related bugs in the current code base.


What is the rust communities obsession with suggesting (threatening?) rewrites of battle tested C programs that have been around for decades?

Now a kernel for a new OS, that'd be something.


The article covers both of your points:

> Rust is supposed to be an excellent systems programming language, having the ability to go extremely close to the bare metal, while offering a thick layer of security and expressiveness which C lacks of. And such an assumption has to be trialled!

> Which Philipp Oppermann has already done by beginning to write a real OS in Rust and writing about it. But this is not a realistic trial: an OS starting from scratch has almost zero chance to end up being finished, not to mention being actually used on real machines.

That is, a more accurate title might be "Proof of concept of incrementally rewriting the Linux kernel in Rust".


I provoked some solid answers from team members on that in this thread:

https://lobste.rs/s/ldnlos/have_you_considered_rewriting_it_...

Skade even dropped a HOWTO in one comment on replicating much of their community results in other projects. There's plenty of data from team members to look at there where I'll let the rest of you make decisions about it on your own.


It would cease to be Linux, but by all means -- make it so!

BTW Redox OS (written in rust) originally supported Linux syscalls which I thought was a super cool feature.

Jorge Aparicio started steed, a libc implemented in rust for rust. It seems like a pretty interesting approach.


Author said:

Thank you. Actually, I have a series of articles about a libc written in Rust for Rust which I started writing before steed was published, and… isn’t finished yet. :-/ But maybe, I will finish and publish it eventually…


> (*pointer).handle();

Eek! That is, indeed, unsafe.

But this shouldn't be messing with the entry asm at all. Just add your new syscall to the syscall table, just like any other syscall.


This was a fun write up.

On the off chance the author is reading the comments here, there are a couple security features they need to add for it to be more complete (I'm probably missing some things).

1) They should add a call to access_ok(VERIFY_WRITE, pointer, mem::size_of<Syscall>()) in rust_syscall_handle to ensures that the user space pointer points to valid userspace memory of the right shape for the syscall type.

2) They should sanitize (hopefully, using some zero-cost method) the Syscall type to guarantee that it is well-formed before calling handle. If the userspace constructs some normally impossible to construct Syscall, it's unclear how a rust match pattern handles it (i.e., if an enum has only 3 types and you pass in a falsely constructed variant with tag 100 is match guaranteed not to just be using a jump table and accidentally jump 100 instructions?)


Don't know about HN but a friend of the author posted this on reddit and was commenting there: https://www.reddit.com/r/programming/comments/6f235k/rewrite...

edit: they also submitted it on HN, but it looks like they didn't get the "winning" submission: https://news.ycombinator.com/submitted?id=fjallidergodur


I am said friend, we're reading the discussion.

I'd rather see a rewrite of QNX in Rust, as open source. The QNX kernel isn't very large, but it offers most of POSIX, so you can run programs on it. (L4 is nice, but it does so little that people just use it as a hypervisor to run Linux. This doesn't simplify the problem.)

If you rewrite the Linux kernel in Rust, one module at a time, you'll just end up with C written in Rust syntax, with raw pointers all over the place.


I was thinking the same thing but for http://www.minix3.org/

fun anecdote, it seems that minix3 is the ancestor of the OS running in Intel Management Engine http://blog.ptsecurity.com/2017/04/intel-me-way-of-static-an...

QNX is also realtime.

In order to provide realtime guarantees that cover small latencies, you need infrastructure that generates as few instructions as possible. (If you have lots of instructions you can hit targets of a hundreds of microseconds since you can just wait. Start inching toward nanoseconds, though, and the scheduler's own execution will get in the way unless it's crazy compact.)

The reason I mention this is that QNX runs on a variety of embedded devices (http://qnx.symmetry.com.au/ has an interesting list, universal remotes particularly jumped out at me) which are traditionally quite slow.

What's Rust like in terms of generating code that's consistently compact?


   What's Rust like in ...
Isn't that less a question of Rust (which due to its affine types, should be usable for generating code with predictable performance) but of compilers? It should be possible to write a Rust compiler that optimises for predictable performance at the cost of speed.

That's a fair point.

I don't quite follow why speed needs to be sacrificed to get predictable performance though.

And I don't know about writing a (whole new?!) Rust compiler (!) - if the current Rust compiler wouldn't be a good fit for this purpose, well, I definitely don't have the resources to anything about that, considering the unbelievable amount of attention to detail and wrangling that's gone into getting rustc to where it is today.


Everything you say is true, but a very simple compiler with little/no optimisations should be quite easy to write. Indeed it is often coursework in undergraduate compilers courses (for source languages simpler than Rust). The main difficult would be the type-checker, but you could reuse the existing one, for it does not affect run-time performance.

It's a fun idea and many people have had it first thing when they heard of Rust... So why did no one do it?

Quite simply: No one is going to rewrite the Linux Kernel in Rust. It is far too big and also you are not solving any real issues either. Rust only protects you from a small fraction of errors and while for an application like a browser, this can be a big gain, I would argue that it is negligible for a kernel in general. Reasons being that all the device IO, component interaction, privilege escalations, logical errors, hardware errors, firmware errors/bugs all can NOT be addressed by rust. Even for a browser, Rust is only a band-aid. The amount of logical errors and security holes in something as complex as a modern web-browser is more than enough of an attack surface. No need for a rouge pointer to weird memory.

What is MUCH more viable though is a project to compartmentalize the Linux Kernel into HVMs. I forgot the name but there are efforts to put nearly everything into its own HVM. Which means if the printer driver goes nuts, it can't really do anything to your system except not print anymore. If your graphics driver goes nuts, well then you won't see anything... And so on.

This means, almost no code rewrites and still MUCH higher protection than RUST. Rust does not compartmentalize. If any of your system components is fucked, your whole system is still fucked. That is why it's pointless to rewrite a kernel because of a language. You need to compartmentalize it...

Look at QubesOS for an early user-space effort. Would be nice to have a Qubes-Kernel too.


You're referring to microkernel/nanokernel architectures.

Linus famously shunned Andrew Tanenbaum's MINIX kernel design and argued in favor of a monolithic kernel, where buggy printer drivers live in the same memory space and have the same elevated privileges as the code that manages the kernel's secure crypto key ring.

Linus is also noted in this thread as not being interested in security issues.

I do agree with you about Rust not solving the hardware problem.


Yes it also makes sense. Because no matter what you do, it is unlikely that your are able to compartmentalize something so critical and raw as a device driver. If that one is fucked, the reason is likely that your system is already compromised. It was just an example of what WOULD be more viable and effective than rewriting it in Rust.

I still think that QubesOS is taking the right approach. Initially assume hardware & kernel as trusted and make sure that this trust then can not be violated from the outside (TPM, SecureBoot, VMs for each app, etc.). I just wish more people would focus on that promising approach.


I agree.

Writing in Rust might make the iteration/development process itself a little easier, I'll give it that.

I also like the idea about creating a system that does its best to isolate components and create a trustworthy environment. I'd probably pull the enforcement into the kernel though - not quite SELinux/grsecurity/AppArmor, more a system that completely isolates everything.

Kind of like the direction Linux is going with containerization, but developed that way from the start.


> able to compartmentalize something so critical and raw as a device driver.

QNX did this quite successfully; you could kill and reload e.g. a buggy network or disk driver. All that on top of being a realtime OS.


Isn't that what Redox-OS [1] does? Though it is not fully POSIX compliant for decisions the development team made.

[1] https://www.redox-os.org/


Once Rust has been available as the main attack surface in a browser for several rounds of "pwn-to-own" I think people will come to grips with it not being a magic bullet.

I think you are right about the Qubes approach. Microsoft has started adopting that model in Windows. They started by moving parts of LSASS to a compartmentalized trusted code base in a VM. (Credential Guard). I think the next thing is actually running Edge with some hypervisor protections. Can't remember the feature name, though.

But that kind of thing is the future. You have to assume that things will be breached and deal with that upfront.


> No one is going to rewrite the Linux Kernel in Rust. It is far too big ...

Please refrain from using strawman arguments. Nobody proposed to rewrite everything at once. This is what TFA actually wrote:

| So the idea would rather be to rewrite pieces of the Linux kernel in Rust, so the change can be incremental and one doesn’t need to rewrite the whole OS

See also: https://news.ycombinator.com/item?id=14479559

(It puzzles me what this strawman was good for, given that the remaining arguments are independent from the "too big" argument anyway.)


> you are not solving any real issues either. Rust only protects you from a small fraction of errors and while for an application like a browser, this can be a big gain, I would argue that it is negligible for a kernel in general. Reasons being that all the device IO, component interaction, privilege escalations, logical errors, hardware errors, firmware errors/bugs all can NOT be addressed by rust.

Would something like Ada SPARK be more helpful here? And what critical parts of that is Rust missing?



I guess one of the things that always comes to mind when someone brings up "Rust all the things" is that it assumes that anything that can be expressed in C can be expressed in Rust with the safety features enabled. Rust wouldn't have a mechanism to do unsafe code otherwise, right? I think it might be more complicated than we might think initially.

I personally think that someone should probably spend some more time on GC that's performant enough for systems code. My convictions haven't driven me to do anything about it, however.


Safe rust clearly cannot express everything that can be expressed in C. It cannot even express everything that is safe in general. But "the safety features of Rust" includes drawing a barrier between the safe parts of your code and the unsafe parts. There is significant value in that compartmentalization. Moreover, you are assuming that things written unsafely in C should be written identically in rust, when there very well could be more idiomatic, modern ways to do the same thing without any unsafety at all.

There are modern ways to express something safely that is written unsafely in C. What Rust evangelists seem to forget is that there have been ways to do that for a long time.

The article actually has very little safe Rust in it. It makes me wonder what we are gaining? If the idea is that introducing Rust means that later you can go back and add type and memory safety...why not use C++ in a type and safe by construction way? Why not use Safe D?

I'm personally just looking forward to the hype dying down. I'm tired of hearing about it, and most of the time I just click "hide" on Rust articles because I'm not interested and people are starting to sound pretty shrill.


Have you ever written anything in Rust?

Like I said, I'm tired of hearing about it and, no, I haven't.

>Rewrite Linux Kernel in Rust? Rust has no backward compatibility: C code compiles fine after decades. It would be a waste of time to rewrite Linux in Rust when the resulting code has a lifetime of few months until it won't compile and start breaking everywhere.

Rust has had backward compatibility since 1.0: https://blog.rust-lang.org/2014/10/30/Stability.html

Servo and most projects use nightly which breaks it all the time.

But you wrote "Rust has no backward compatibility" who cares what libraries/projects do?

Rusrc nightlies are the compiler. The only notable compiler i am aware of. There is no standard, ecosystem or any -std=v1.0 that old code can rely on. >who cares People who want to invest time in Rust.

Servo might use nightly, but do you have evidence that most projects do? Especially after custom derive landed in stable, most of the reason for sticking with nightly dissappeared.

> C code compiles fine after decades

Early C code doesn't compile on modern systems at all.

Speaking of early C code, every vendor had their own version. C standardization had to go a long way.

Even today, writing portable C code is hard. Need a portable 128-bit integer? Good luck on Windows. Need a portable 32-bit integer? Better take a C implementation that provides int32_t, because just using "int" bites you when switching from 32-bit to 64-bit platforms.

Oh, and int32_t was introduced in C99, which is not exactly "decades" ago, but admittedly that was 18 years ago. However, I wouldn't bet on portability of any code older than that, unless the authors of that C code were extremely careful and anticipatory. But then, with enough effort (and perhaps code generation), you can create portable code in any language.

Rust skips all that mess, by having a clear experimental phase in the 0.x versions. It became stable with a clear commitment to backwards compatibility since 1.0, which was released in 2015.


> Need a portable 32-bit integer? Better take a C implementation that provides int32_t, because just using "int" bites you when switching from 32-bit to 64-bit platforms.

Citation needed. Which compilers for which machines are you talking about? I don't think any change the size of int. What does break is the assumption that int is the same size as pointers, but that's because the sizes of pointers change.


Whoops. I meant 16-bit versus 32-bit integers, when switching from 16-bit to 32-bit systems. Sorry for the confusion. Fortunately, they didn't repeat that mess for 32/64 bits, at least not for integers, but "decades old code" is still affected.

Ah, yes. I agree, that was a mess!

> C code compiles fine after decades

... and then hilarity ensues at runtime due to latent UB that happened to "work" in the C implementation the original author used.


Legal | privacy