Skip to content(if available)orjump to list(if available)

Imposing memory security in C [video]

Imposing memory security in C [video]

46 comments

·February 27, 2025

pizlonator

To me, "memory safety" really means:

- There are clearly stated guarantees, like "a pointer cannot access outside the bounds of its allocation" and "if you free an object while there are still pointers to it then those pointers cannot be dereferenced". These guarantees should be something you can reason about formally, and they should be falsifiable. Not sure this presentation really has that. It's not clear what they prevent, and what they don't prevent.

- There is no way to break out of the clearly stated guarantees. Totally unclear that whatever guarantees they have are actually guarded against in all cases. For example, what if a tmp_alloc'd object pointer escapes into another tmp_alloc'd object with different lifetime - I get that they wouldn't write code that does that intentionally, but "memory safety" to me means that if you did write that code, you'd either get a compile error or a runtime error.

It's possible to ascribe clearly stated guarantees to C and to make it impossible to break out of them (Fil-C and CHERI both achieve that).

kstrauser

Thinking aloud, and this is probably a bad idea for reasons I haven’t thought of.

What if pointers were a combination of values, like a 32 bit “zone” plus a 32 bit “offset” (where 32/32 is probably really 28/36 or something that allows >4GB allocations, but let’s figure that out later). Then each malloc() could increment the zone number, or pick an unused one randomly, so that there’s enormous space between consecutive allocs and an address wouldn’t be reissued quickly. A dangling pointer would the point at an address that isn’t mapped at all until possibly 2^32 malloc()s later. It wouldn’t help with long-lived dangling pointers, but would catch accessing a pointer right after it was freed.

I guess, more generally, why are addresses reused before they absolutely must be?

zyedidia

It sounds like what you're describing is one-time allocation, and I think it's a good idea. There is some work on making practical allocators that work this way [1]. For long-running programs, the allocator will run out of virtual address space and then you need something to resolve that -- either you do some form of garbage collection or you compromise on safety and just start reusing memory. This also doesn't address spatial safety.

[1]: https://www.usenix.org/system/files/sec21summer_wickman.pdf

kstrauser

Oh, nifty! I guarantee you anyone else discussing this has put more than my 5 minutes' worth of thought into it.

Yeah, if you allow reuse then it wouldn't be a guarantee. I think it'd be closer to the effects of ASLR, where it's still possible to accidentally still break things, just vastly less likely.

pizlonator

That’s a way of achieving safety that has so many costs:

- physical fragmentation (you won’t be able to put two live objects into the same page)

- virtual fragmentation (there’s kernel memory cost to having huge reservations)

- 32 bit size limit

Fil-C achieves safety without any of those compromises.

kstrauser

For sure. I'm under no illusion that it wouldn't be costly. What I'm trying to suss out is whether libc could hypothetically change to give better safety to existing compiled binaries.

throwawaymaths

you can do this easily with virtual memory, and IIRC Zig's general purpose allocator does under some circumstances (don't remember if its default or if it needs a flag).

johnnyjeans

> There is no way to break out of the clearly stated guarantees.

I disagree on this, and having escape hatches is critically important. Are we really going to call Rust or Haskell memory unsafe because they offer ways to break their safety guarantees?

pizlonator

I think that Rust's clearly stated guarantee holds if you never say "unsafe".

That's still a clear statement, because it's trivial to tell if you used "unsafe" or not.

the__alchemist

Trivial is not a word I would use here! Rust's `unsafe` gets fuzzy as you transverse an operation's dependencies! There are many applications where marking a function as `unsafe` is subjective.

johnnyjeans

Maybe I just misinterpret what you mean. When you say "no way" and "all cases", I take your meaning literally. The existence of pointers to bypass the borrow checker, disabling runtime bounds checks and unsafe blocks are exactly that: escape hatches to break Rust's safety, in the same way type-casting is an escape hatch to break C's (anemic) type safety, and unsafePerformIO in Haskell is an escape hatch to break every bone in your body.

jeffrallen

But the Rust ecosystem is littered with unsafe, so good luck getting the actual benefits of Rust. :(

PaulDavisThe1st

> There are clearly stated guarantees, like "a pointer cannot access outside the bounds of its allocation"

But that's not a pointer in anything like the sense of a C pointer.

You'd need to reword that (as I know you've been doing with FiL-C) to be something more like: no reference to a (variable|allocation|object) may ever be used to access memory that is not a part of the object.

Pointers are not that, and the work you've done in FiL-C to make them closer to that makes them also be "not pointers" in a classic sense.

I'm OK with that, it just needs to be more clear.

pizlonator

Semantics.

You can call Fil-C’s pointers whatever you like. You can call them capabilities if that works better for you.

The point of my post is to enumerate the set of things you’d need to do to pointers to make them safe. If that then means we’ve created something that you wouldn’t call a pointer then like whatever

tredre3

> Semantics.

If your goal is just to redefine the word then by all means, continue.

But semantics are very important if your goal is to drive adoption of your ideas. You can't misuse a term and then get pissy when people don't understand you.

PaulDavisThe1st

And my point is that you cannot make C pointers safe. You can make something else that is safe, and you're clearly hard at work on that, which is great.

debatem1

I don't think anyone ever doubted that a C program could be memory safe. The problem is knowing without exhaustive work whether yours is one of them.

These aren't bad practices, but I don't think they satisfy that desire either.

cryptonector

Nah. I use C a lot, but none of this is enough to make C safe. You really need the language and the tools to enforce discipline. Oh, and things like the cleanup attribute are not standard C either, so this is not portable code.

throwawaymaths

usually portability in C includes the provision that you can drop in whatever #includes you want?

beardyw

I remember chasing down a memory leak in my first commercial C code. Took me a long while to discover that if you allocate zero bytes you still have to free it! After that I took nothing for granted.

weinzierl

It's not even guaranteed that it doesn't allocate, so a malloc(0) could cause an out of memory.

ignoramous

> malloc(0) could cause an out of memory

tbh, 640K RAM ought to be enough for anybody.

SV_BubbleTime

I am in no way at all better than that guy. Not even sort of. I appreciate his talk.

However, if I were to make a presentation based on my superior C practices, it would have to be implementation and example heavy.

All of his rules sound great, except for when you have to break them or you don’t know how to do the things he’s talking about in your code, because you need to get something done today.

It reads a little like “I’ve learned a lot of lessons over my career, you should learn my lessons. You’re welcome.”

rrrix1

The talk was obviously extremely time-limited, as demonstrated when they basically skipped the last handful of slides and then it abruptly ended. I think for the time allocated, it was just right, and they did include a couple of examples where it made sense.