4 ms·
What they do is make things safe by default with a keyword to turn it off. Ada and PL/S (IBM's) do this, too. They also have other keywords to tell the compiler
by nickpsecurity 8y ago
What they do is make things safe by default with a keyword to turn it off. Ada and PL/S (IBM's) do this, too. They also have other keywords to tell the compiler how to handle some statements that might be undefined in C. For unsafe, the module will either use the same language without safety or GC, or assembly language, or some combo of that. Developers using such features often have type checks and input validation on their arguments since their usage is dangerous. So, the majority of the code is using safe primitives with GC while specific parts that can't are behind protected interfaces.
The next thing you can do is use lightweight formal methods, static analysis, etc on the unsafe code. So, those primitives are verified in unsafe HLL or in assembly. You know they'll probably act right given preconditions are met. Then, the rest of code is protected by type- and memory-safety. Combining best tool for each job makes your whole system safe despite unsafe code.
One person I know had a successful test of using SPARK Ada for fast, otherwise-unsafe components of Go applications. My Brute-Force Assurance idea would likely, given most use C language underneath, do these in Frama-C compiled with CompCert or C0 after I threw every static/symbolic analyzer, concurrency checker, and fuzzer at it. Very unlikely to have trouble out of anything that passes all that if the spec or mental model itself is correct. Rest is safe, memory-managed code. It also helps to keep same data types, calling conventions, and so on to prevent abstraction-gap attacks. There's ongoing work on abstraction-safe, verified integration of components in different languages, though.
Also, note that you can use different memory managers in different apps or even parts of an app. The runtime has to be designed to do that. JX OS in Java is one that is. There are GC's optimized for throughput, average latency, hard real-time, size, and so on. A standard allocator gives the code a block to use, they assign to or divide it up for the other GC's, and they work from there. There might also be some work to do on scheduling how they run, too. I can't remember how easy or difficult that is on a common platform. It was mostly done on custom OS's.