Erase dealocated Memory

Is there a way to ensure that deallocated data is erased on the physical memory? E.G. If I have some super secret stored value like a private key in a swift class and I want make sure, that no trace is left on RAM after deallocation of that class.

1 Like

For classes, you can zero-d out data during deinit.

1 Like

Is there a special command, or a standard code snippid to do that properly?

I don't know if there is one, but if you can get the pointer to the data, you can use memset. Though I do not know if it is safe enough for security purpose.

Is there a way to ensure that deallocated data is erased on the
physical memory?

Not really. There’s lots security theatre you can apply here, but it’s a rare day where those measures actually achieve anything. This question crops up regularly on DevForums, and you can find my standard responses on this thread (make sure to scroll down to the 17 Jul 2015 post).

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

If I might add to what Quinn wrote, there are a lot of moving parts that are not under your control that can create copies of memory that you can't realistically control or even track. For example, Swift doesn't allow users to supplement the destructor logic of value types, so something like a String could have been copied implicitly or explicitly many times over. Even if you don't store a password in a String and even if you explicitly use a class to store the password, the OS can move and copy memory around as it pleases (for example, paging).

Personally, what matters most is that sensitive data should never be reachable from long-lived data structures. In other words, if your program has the ability to "debug dump" internal state, then sensitive data shouldn't be in the output when the program is idle.

1 Like

Ignoring the other concerns for a moment, I can answer the question about memset: it’s not sufficient. memset_s exists for this use case.

2 Likes

Does it help to call the destructor and overwrite all entries of a class with zeros or something less important before dereferencing?

In particular, the optimizer will eliminate the C function memset if it can prove that it's the last thing that happens before an object is released to the system, because the memset has no observable effect on the program's execution when that's the case (it effects the state of system memory, but that's outside the scope of the abstract machine that defines the language semantics).

memset_s is guaranteed to zero the memory, because it does not receive this special treatment from the optimizer. FWIW, I think it would be perfectly reasonable to have Swift map memset to memset_s (or other equivalent operation), to avoid this particular pitfall.

But: even securely zeroing a buffer doesn't guarantee that it's gone, since the compiler is free to generate temporary copies on the stack or in register or on the heap, and--since those have no name in the your program's source--you have no way to zero them.

2 Likes

but on a jail broken device, every app's memory can be dumped using another app.
why there is no way to manage the memory to prevent data from staying there?

If a malicious actor can read your memory, you have already lost. It is common for security-conscious apps to refuse to run on jailbroken devices for exactly this reason.

3 Likes

I generally agree, but there's an element of "protecting your app from itself". If you have a Heartbleed style buffer overrun, your own app might read from its own process space (which is obviously totally allowed) and leak its own secrets.

Obviously one could just say "don't have buffer overruns in the first place," but that's proved itself to be quite a challenge. A memory safe language like Swift definitely helps, but at bottom, we still fall back to a fair bit of Unsafe* code (and equivalently, libraries written in C).

All that said, I don't know how much it helps to proactively zero out secrets, or if it's just security theatre.

Note that on recent versions of Apple platforms (at least since 2022), free(3) does in fact zero memory when it's deallocated, and Swift objects are ultimately deallocated using free.

7 Likes

Interesting! Do you know what "tipped the scales" in favour of doing that?

How much of a perf penalty does it have?

Since the XNU kernel uses memory compression as a first defense against swapping or killing idle UI processes under memory pressure, at system scale it ends up being a net performance win, since zeroed memory compresses better.

9 Likes

but there is no way to know for 100% that the device is jail-broken. I run the checks but they are no robust

Please don’t resurrect threads that have been dormant for several years.

1 Like

I am running this on M1 Pro, under macOS Ventura 13.4

var x = strdup("ABC Hello, World! Hello, World!!, Hello, World!!!")!
print(x[0]) // 65
print(x[1]) // 66
print(x[2]) // 67
free(x)
print(x[0]) // -24
print(x[1]) // -65
print(x[2]) // -34

I expected to get 0's after free, tried in both Debug & Release. Is this a bug? Or perhaps I am not testing this properly, in which case would appreciate the fix. Thank you.

Edit 1:

Tried this modification to not cause any potential allocation between free and reading the freed memory out, but the result is the same:

var x = strdup("ABC Hello, World! Hello, World!!, Hello, World!!!")!
var a = 0, b = 0
memmove(&a, x, 8)
free(x)
memmove(&b, x, 8)
print(String(format: "%016lx, ", a)) // 6c6c654820434241, as expected
print(String(format: "%016lx, ", b)) // 0000beaddfdd00c0, expected to see 0's here

Edit 2:

Or do you mean the memory it is cleaned, but I should just disregard the first pointer or so? hmm, let me double check that.

Edit 3:

Indeed!

var x = strdup("ABC Hello, World! Hello, World!!, Hello, World!!!")!
var a = 0, b = 0
memmove(&a, x+16, 8)
free(x)
memmove(&b, x+16, 8)
print(String(format: "%016lx, ", a)) // 2c6f6c6c65482021, as expected
print(String(format: "%016lx, ", b)) // 0000000000000000 as expected
2 Likes

For tiny allocations like that, it looks like libmalloc stores free block metadata in the freed slots themselves:

So that's probably what's happening here. You can see a bit further up from there that if the freed magazine slot can be merged with the previous block it gets zeroed.

2 Likes

Yep, I figured I just need to look further into the memory (updated above).