FreeBSD security design flaws

The kernel can't do only memory safe operations by definition. That's why we separated the kernel from userland. The two serious attempts at a Rust OS are Redux and Asterinas. And at least one of them tries to separate the kernel in two: part with unsafe Rust, part with safe Rust. The other uses a microkernel, which will never work, and completely mixes safe and unsafe Rust together. Again, a pure safe Rust kernel is theoretically impossible: the kernel must do unsafe memory operations just as basic operations. Example: A user program calls read(). The kernel must copy bytes from a kernel buffer into a pointer the user provided. That pointer could be: null, unmapped, a kernel address (privilege escalation attempt), and maybe something else. In any case, the kernel must do an unsafe instruction to that pointer to do its job. There is no safe version of this operation

Memory safety is largely misunderstood and the arguments of it's proponents mostly come from people that don't understand the basics.

Even "Permissions are attached to users, not capabilities" is a failure of basic comprehension. Why and how are capability-based systems an obvious upgrade? How much resources went into seL4, Fuchsia/Zircon and other attempts? It's not even clear that such systems are well-defined.

There's also a failure to understand the actual historic sources of security flaws. Major kernel exploits don't usually come from "a rarely-used USB driver" being isolated. They come from core subsystems (memory management, syscall handling, privilege transitions) that a microkernel still has in its privileged layer. So you're really fixing a non-existent theoretical flaw and trading that for a geometric increase in transactions and complexity.
 
And at least one of them tries to separate the kernel in two: part with unsafe Rust, part with safe Rust.
I feel it NOT logical.
Using Rust for memory-unsafe part would easily make confusions in the future.
Using C, asm or some other low to mid level language for unsafe part to clearly mark that the part is MUTUALLY memory-unsafe in mature would be far more cleaner. This way, UNSAFE part of Rust codes can be definitions for interfacing with C / asm / something else. Far more saner.
 
See r9os/r9 on github. It's a reimplementation of the plan9 kernel in Rust. There are 249 lines using unsafe, out of 9620 lines of rust code (and 884 lines of asm code). No idea how well it works or how complete but it shows that actual unsafe code is pretty small part of the kernel. No one is looking for "purity", it is more a matter of reducing the scope for screwing up.

My personal opinion is that given a better hardware design there would be *no* need for a higher privileged kernel. The *first* program to run after powerup would hold *all* the access rights to everything in a system (at hardware level) and a more structured system would be constructed from it (or "evolved") by ladling out subsets of access rights to more specialized processes. Various implementations of CHERI's hardware arch will hopefully provide a proof of concept which can then be analyzed (though AFAIK, such implementations still do have a higher privilege mode). Whether such an arch. ever becomes popular is unknown (but IMHO unlikely).
 
I feel it NOT logical.
Using Rust for memory-unsafe part would easily make confusions in the future.
Using C, asm or some other low to mid level language for unsafe part to clearly mark that the part is MUTUALLY memory-unsafe in mature would be far more cleaner. This way, UNSAFE part of Rust codes can be definitions for interfacing with C / asm / something else. Far more saner.
You're correct, of course. But you've not met Rust evangelists. They don't even have those concepts in their periphery.

Edit: Not ASM. I want my OS portable.
 
Edit: Not ASM. I want my OS portable.
As soon as you write low-level code, it's not portable, in C as well as in ASM.
C:
void hal_timerSchedInit(void)
{
    // Set up timer1 interrupt for scheduler
    TCCR1B |= (uint8_t)(1u << WGM12); // CTC mode
    OCR1A = TIMER1_OVERFLOW_COUNT;
    TIMSK1 |= (uint8_t)(1u << OCIE1A);
}

This C code for atmega2560 is highly not portable, we have to go through HAL to get a portable code. There is no OS without ASM, even if it's just to make the initial wiring hardware -> C/compiler ABI.
 
This C code for atmega2560 is highly not portable,
Of course not, because all what's C about it, is this:
C:
void hal_timerSchedInit(void)
{
    // Set up timer1 interrupt for scheduler
    |= ()( << ); // CTC mode
     = ;
     |= ()( << );
}
Because
C:
    TCCR1B
    uint8_t
    1u
    WGM12
    OCR1A
    TIMER1_OVERFLOW_COUNT
    TIMSK1
    OCIE1A
are somewhere defined, like in some library.

The point is, there is no 100% portable language, except it runs within some kind of a VM or interpreter, for which then first a version needs be written that runs on the according hardware, like shell or Java for example.
C never claimed to be 100% portable, it claimed to be more/better portable than assembling language.
To stay in the example you've given, the boilt down idea is to have seperated files adapted to each machine (as far as possible), where the variables and registers are defined, which then in combination with the first file (above) needs to be compiled individually for each architecture, so the algorithm can be used on different machines without being completely rewritten, but only the definitions needs to be adapted.
This of course does not work in every case, of course, but it's anyway far better than have it all fixed integreted as and in the code, and above all assembler.
 
Edit: Not ASM. I want my OS portable.

If I recall correctly, it's one of the reason why C is developed.

This EXACTLY :) ! They were porting the same C code across DEC PDP- (multiple generations - aka 7, 8, 9, 10, 11, etc) and other machine architectures.

This is why I became a big fan of "C" and UNIX decades ago -- instead of being stuck with a vendor supplied O/S on your hardware -- (think VMS, RSTS/E, TOPS, DOS (PC or Microsoft), Windows, etc) you could run your favorite UNIX release on the same hardware instead.
 
Portability doesn't depend solely on the language, but especially on the existence of compilers for multiple HW arch. Portability begins early before the compiler, with the selection of source files at build time and then finishes at runtime with vtables or VMs. Portability also depends on the environment: C applications are very portable because we have a libc everywhere, itself written in C or ASM.

It is in this general description that the C + Unix tool chain becomes a powerful and portable tool.
 
If I recall correctly, there had been CPUs directly run JAVA bytecodes as its instruction set. But not become majority.

If someone tries something like it now, the instruction set can be LLVM/IR.
 
Portability doesn't depend solely on the language, but especially on the existence of compilers for multiple HW arch.
Well, yeah. Those are exactly the two points of why there are HOL:
  • No need to fumbling with assembler language
  • Having portable source code. It's the job of the compiler (or interpreter, or VM) to make it run on the according architecture. Those need to be written for the machine first of course.
I don't know, but I believe it's also because of it's closeness to Assembler, that so many C compilers have been written for so many architectures. There are C compilers for really tiny machines, and also pretty small C compilers.
I'm also convinced that gcc have a great contribution with the fact that C is available for so many platforms. Don't want to stress details here (this thread is 💩 by it's very start anyway - ah I love, that I'm not the only one, hijacking a pointless 💩-thread for inofficial off-off-off-topic chitchat 😂), so thanks to all guys who participate(d) in gcc since its very beginning. 🙏
You mentioned microcontrollers. On those things you have to program in Assembler sooner or later anyway, sometimes even better in the first place directly. For example when reliable, precise, exakt timing at CPU's clock speed is important (e.g. fast technical processes [e.g. motor RPM control]), there is only Assembler on bare metal. Your example shows it: registers, timers (registers), interrupt (registers)...- placing, comparing, testing...shoving bits all the time. What you do there ain't nothing what you wouldn't do else in Assembler. It's just embedded in a C framework, 'cause it looks a bit nicer to say "A = B" instead of "MOV A, B", but in the end it's nothing else but handling bits.

While there is also one additional thing for portability not mentioned so far, because with most today's machines we don't really look at it so closely anymore, 'cause we are used to in most of the cases there is way more than plenty of it: Memory.

That's why I use the term "in theory" when I say, 'with addition and complement you can create substraction, muliplication and division. Having those any math can be realized.' In theory. When there is enough memory. I don't really know, but I believe it could be quite a problem to write some FFT in C++ or Rust for a 8bit microcontroller with 4kB RAM, even if there was some (cross)compiler capable for doing that in theory. 😁:cool:

botm.jpg
 
I really enjoy posts like these because it tells you all you need to know about the context: people who apparently blindly rely on their OS (or "equipment") to keep them safe which - by definition - is the biggest security risk of them all. Most intrusions were initiated from the inside; the human (or "user") is always the weakest link.

1. The Ambient Authority / DAC Model (The Core Problem)​

This is arguably the root of everything else. FreeBSD uses Discretionary Access Control (DAC) — the classic Unix user/group/other permission model. The fundamental flaw:
  • Permissions are attached to users, not capabilities
  • Any process running as a user inherits all of that user's rights
  • root is a god-mode binary switch — you either have it or you don't
  • A compromised process gets the full authority of whoever launched it
Pretty flawed example, because it completely ignores a lot of important details. First: a regular user can only do so much. Second: users can be limited in their direct access to a system, either through a limited shell, a chroot or maybe using a virtual environment such as a jail. And of course: resource limits are also a thing.

On one of my more delicate servers several use IDs are limited in the amount of processes they can have active at the same time. Meaning? Even if the process itself had an exploitable flaw which would allow an attacker to spawn an extra process then the OS itself will simply deny this, exploit or not, because the limit on the amount of active processes has already been reached for that ID.

So no: a compromised process does not necessarily get full authority.

Another issue: K.I.S.S. A broader security model also implies more details where things could go wrong.

2. Monolithic Kernel​

FreeBSD runs device drivers, filesystems, and networking stacks in kernel space. A bug in any driver is a bug with full ring-0 privilege. This means:
  • A vulnerability in a rarely-used USB driver can compromise the entire system
  • The kernel's attack surface is enormous
  • Memory corruption in one subsystem can affect everything
The only "problem" here is that in order to exploit such a bug you'd need to have direct access to the system.

And once again this comment ignores a lot of important details. It more or less implies that a kernel always comes with every available driver out there, which is of course nonsense. Sure, if you blindly rely on the OS to keep you safe, but ... I already mentioned that this is a security issue in itself.

Thing is... plenty of servers (mine included) use customized kernels. They only support whatever is needed and hardly anything else. So... in the unlikely event that a driver does have an issue it still remains to be seen if that would make such a kernel vulnerable as well.

3. No Mandatory Integrity/Label Model by Default​

FreeBSD has MAC framework (Mandatory Access Control) and TrustedBSD hooks that can enforce label-based policies, but:
  • It ships disabled by default
  • Configuration is complex and rarely used in practice
  • The labels system doesn't extend cleanly to the network stack
This is of course a complete non-issue. Because once again: people who blindly rely on their equipment to keep them safe... are a security hazzard of their own making.

4. The setuid/setgid Mechanism​

FreeBSD inherits the deeply flawed setuid bit — a file-level flag that says "run this binary as its owner, not the caller." Problems:
  • Any bug in a setuid binary is a privilege escalation vulnerability
  • The attack surface has existed for 50 years and keeps producing CVEs (sudo, pkexec, etc.)
  • There's no sandboxing of what a setuid binary can actually do
No sandboxing? I guess you never heard of Jails then. You also claim that this is a huge issue, but the irony is that you just acknowledged the fact that MAC exists?

A proper fix requires fine-grained privilege delegation — no program should need to run as root just to bind a port or write a log. FreeBSD's capsicum(4) framework is a good step here, but adoption is limited.
See, I have a problem with that. Right now it's either you can access these areas or you can't. More fine grained control also introduces more areas where things could go wrong.

My problem right now is that you in every example so far you point out the risk of bugs. However, because of the rather straight forward model right now those areas are somewhat limited and easily monitored. The more layers of access you introduce the more potential bugs you're going to introduce.

Note: I'm not claiming that things are perfect, but the suggested alternatives also come with their own set of nightmare issues. Yet you conveniently ignore all of that for some reason?

5. Capsicum Exists But Isn't Ubiquitous​

Capsicum is actually FreeBSD's best security innovation — a capability-based sandboxing model that restricts what an already-running process can do. It's elegant. The problem:
  • Developers must opt in manually with cap_enter()
  • Almost no third-party software uses it
  • It's not enforced on system utilities by default
Once again with the "enforced" delusions. Enforcing stuff on people does not increase overall security. I guess you never worked in an office where management forced strong passwords on its users?

I have. And the direct results were obvious and predictable: dozens of yellow sticky notes with passwords written down, making the whole situation potentially more vulnerable than before. But ey... at least they had mandatory hard passwords!

Security isn't a black/white rulebook which you can throw at an environment after which things get better by definition, that's just corpo talk.

Summary Table​

I take it we can expect your new contributions to the source tree within a few months from now?
 
Back
Top