Reputation:
When we free()
memory in C, why is that memory not filled with zero? Is there a good way to ensure this happens as a matter of course when calling free()
?
I'd rather not risk leaving sensitive data in memory released back to the operating system...
Upvotes: 14
Views: 8659
Reputation: 22
The most famous "hack" is to override malloc & free
// alloc & save size
#define salloc(sz) ({ \
const size_t sz_size = sizeof(size_t); \
void *p = malloc(sz + sz_size); \
memcpy(p, &sz, sz_size); \
p += sz_size; \
}) \
// regular free
#define sfree(p) ({ \
free(p -= sizeof(size_t)); \
}) \
// free + wipe memory
#define ssfree(p) ({ \
const size_t sz_size = sizeof(size_t); \
p -= sz_size; \
memset(p, 0, *((size_t *)p) + sz_size); \
free(p); \
}) \
Upvotes: 0
Reputation: 144770
Freed memory is not cleared because in most applications it is not necessary and would take extra time. Note that the memory returned to the system will be cleared before it is made available to other processes, but a root process could try and bypass this safety. Note however that a root process can monitor your process while it uses the memory to try and extract sensitive information.
On linux with the GNU libc you can use malloc_usable_size(p)
to determine the allocated size of a heap pointer. BSD systems have a similar call malloc_size(p)
. To ensure sensitive data is cleared when you free the memory, you can use a wrapper:
#include <stdlib.h>
#include <string.h>
void secure_free(void *p) {
if (p) {
memset(p, 0, malloc_usable_size(p));
free(p);
}
}
You would use secure_free(p)
for allocated data you know may contain sensitive data. Note however that you will need to make sure you free every such block that was allocated. Furthermore, sensitive data might still be lying around in static data, stack space, temporary files, etc.
Furthermore, on modern systems, memory provided to process is automatically cleared to all bits zero, so you can no longer try and find sensitive data by mapping large chunks of memory (as was easy to do in the eighties).
Upvotes: 1
Reputation: 327
"When we free() memory in C, why is that memory not filled with zero?"
Generally speaking, not requiring every freed block of memory to be zeroed upon deallocation lets the compiler generate better code.
"Is there a good way to ensure this happens as a matter of course when calling free()?"
Not really. There are many attempts to solve dead store elimination, but they all have issues with compliance or portability.
This presentation does a great job of summarizing this nuanced issue, and provides a link to their solution's source code, which is reprinted here.
// secure_memzero.h version 1 (October 29, 2016)
//
// This code is released into the public domain.
//
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// The secure_memzero macro/function attempts to ensure that an optimizing
// compiler does not remove the intended operation if cleared memory is not
// accessed again by the program. There are several known ways of doing this,
// however no single one is both universally available and absolutely guranteed
// by the standard. The following code defines secure_memzero as a macro or
// function using one of the known alternatives. The choice of implementation
// can be controlled by defining a preprocessor macro of the form SMZ_impl,
// where <impl> is one of the defined implementation names. SMZ_impl should
// expand to an integer indicating the dgeree of preference for the
// implementation, where numerically higher values indicate greater preference.
// Defining SMZ_impl to be 0 disables the implementation even if it is
// available. Not defining any SMZ_impl will result in default (safe) behavior.
//
// The following implementations may be used.
//
// SMZ_SECUREZEROMEMORY
// Uses the SecureZeroMemory macro/function on Windows. Requires a Windows
// environment (_WIN32 must be defined).
//
// SMZ_ASM_BARRIER
// Uses a compiler memory barrier to force the results of a memset to be
// committed to memory. Has been tested to work on:
// - Clang 3.9.0 at all optimization levels.
// - GCC 6.2 at all optimization levels.
//
// SMZ_MEMSET_S
// Uses the C11 function memset_s. Currently not available on many platforms.
// Note that if you want this option, you have to set __STDC_WANT_LIB_EXT1__
// to 1 before including string.h or any file that includes string.h in a
// compilation unit that includes this header.
//
// SMZ_VDATAPTR
// Uses the volatile data pointer technique to zero one byte at a time. This is
// not guaranteed to work by the C standard, which does not require access to
// non-volatile objects via a pointer-to-volatile to be treated as a volatile
// access. However, it is known to work on the following compilers:
// - Clang 3.9.0 at all optimization levels.
// - GCC 6.2 at all optimization levels.
//
// SMZ_VFUNCPTR
// Uses the volatile function pointer technique to call memset. This is not
// guaranteed to work by the C standard, which does not require the pointed-to
// function to be called. However, it is known to work on the following
// compilers:
// - Clang 3.9.0 at all optimization levels.
// - GCC 6.2 at all optimization levels.
// The remainder of this file implements the selection logic using the
// specified compile-time preferences.
#ifndef _SECURE_MEMZERO_H_
#define _SECURE_MEMZERO_H_
// STEP 1. Set default preference for all implementations to 1.
#ifndef SMZ_SECUREZEROMEMORY
#define SMZ_SECUREZEROMEMORY 1
#endif
#ifndef SMZ_MEMSET_S
#define SMZ_MEMSET_S 1
#endif
#ifndef SMZ_ASM_BARRIER
#define SMZ_ASM_BARRIER 1
#endif
#ifndef SMZ_VDATAPTR
#define SMZ_VDATAPTR 1
#endif
#ifndef SMZ_VFUNCPTR
#define SMZ_VFUNCPTR 1
#endif
// STEP 2. Check which implementations are available and include any necessary
// header files.
#if SMZ_SECUREZEROMEMORY > 0
#ifdef _WIN32
#include <windows.h>
#else
#undef SMZ_SECUREZEROMEMORY
#define SMZ_SECUREZEROMEMORY 0
#endif
#endif
#if SMZ_MEMSET_S > 0
#if defined(__STDC_WANT_LIB_EXT1__) && (__STDC_WANT_LIB_EXT1__ != 1)
#undef SMZ_MEMSET_S
#define SMZ_MEMSET_S 0
#endif
#if SMZ_MEMSET_S > 0
#ifndef __STDC_WANT_LIB_EXT1__
// Must come before first include of string.h
#define __STDC_WANT_LIB_EXT1__ 1
#endif
#include <string.h>
#ifndef __STDC_LIB_EXT1__
#undef SMZ_MEMSET_S
#define SMZ_MEMSET_S 0
#endif
#endif
#endif
#if !defined(__GNUC__) && !defined(__clang__)
#undef SMZ_ASM_BARRIER
#define SMZ_ASM_BARRIER 0
#endif
#if SMZ_VFUNCPTR > 0
#include <string.h>
#endif
// STEP 3. Calculate highest preference.
#define SMZ_PREFERENCE 0
#if SMZ_PREFERENCE < SMZ_SECUREZEROMEMORY
#undef SMZ_PREFERENCE
#define SMZ_PREFERENCE SMZ_SECUREZEROMEMORY
#endif
#if SMZ_PREFERENCE < SMZ_MEMSET_S
#undef SMZ_PREFERENCE
#define SMZ_PREFERENCE SMZ_MEMSET_S
#endif
#if SMZ_PREFERENCE < SMZ_ASM_BARRIER
#undef SMZ_PREFERENCE
#define SMZ_PREFERENCE SMZ_ASM_BARRIER
#endif
#if SMZ_PREFERENCE < SMZ_VDATAPTR
#undef SMZ_PREFERENCE
#define SMZ_PREFERENCE SMZ_VDATAPTR
#endif
#if SMZ_PREFERENCE < SMZ_VFUNCPTR
#undef SMZ_PREFERENCE
#define SMZ_PREFERENCE SMZ_VFUNCPTR
#endif
// STEP 4. Make sure we have something chosen.
#if SMZ_PREFERENCE <= 0
#error No secure_memzero implementation available
#endif
// STEP 5. Use implementation with highest preference. Ties are broken in
// favor of implementations appearing first, below.
#if SMZ_PREFERENCE == SMZ_SECUREZEROMEMORY
#define secure_memzero(ptr,len) SecureZeroMemory((ptr),(len))
#elif SMZ_PREFERENCE == SMZ_MEMSET_S
#define secure_memzero(ptr,len) memset_s((ptr),(len),0,(len))
#elif SMZ_PREFERENCE == SMZ_ASM_BARRIER
#define secure_memzero(ptr,len) do { \
memset((ptr),0,(len)); \
__asm__ __volatile__("" ::"r"(ptr): "memory"); \
} while (0)
#elif SMZ_PREFERENCE == SMZ_VDATAPTR
static void secure_memzero(void * ptr, size_t len) {
volatile char * p = ptr;
while (len--) *p++ = 0;
}
#elif SMZ_PREFERENCE == SMZ_VFUNCPTR
static void * (* volatile _smz_memset_fptr)(void*,int,size_t) = &memset;
static void secure_memzero(void * ptr, size_t len) {
_smz_memset_fptr(ptr, 0, len);
}
#endif
#endif // _SECURE_MEMZERO_H_
Upvotes: 1
Reputation: 57046
C was originally designed as a system implementation language, and so C operations are generally as fast and as close to the metal as is practical. One key point in the design philosophy is that you can take several fast operations and make them into one slower and safer operation, but you can't take slower and safer operations and make a faster one.
If you want a zero-and-free function, you can write one, and use it instead of free()
. If you're concerned with security, I'd recommend it.
Upvotes: 3
Reputation: 279255
[Edit: this is an attempt to answer the original poster's question. The question may or may not have been changed by shog9's edit - it's hard to say since the original was unclear...]
If you mean, as others have assumed, setting 0 for every byte of the memory block being freed, then you cannot do that after freeing the block. Attempting to do it yields undefined behaviour. So if you're doing that, then you have badly misunderstood memory allocation.
But I'm guessing when you say "we set it to zero after freeing", you're maybe talking about code like this:
free(ptr);
ptr = NULL;
If so, then the reason free can't set ptr to NULL, is that free only receives the value from the variable ptr. It has no way of modifying ptr, because you aren't passing the variable ptr itself into free. You're just passing the address currently stored in it. This is part of the design of the C language - when you call a function passing a value, then the callee cannot tell how that value was computed, or what variable might contain it in the caller's code. Making an exception to this language rule just for free would be crazy, even if it were possible.
In any case, not everyone zeroes out pointers after freeing them. Some people think it's a good safety measure, other people think it is not. Whatever you think of it, though, the code doesn't zero the memory, it only zeros the pointer to the memory. If you want to write a function which clears the pointer for you, then you can:
void free_and_clear(void **pptr) {
free(*pptr);
*pptr = NULL;
}
Then use it like this:
free_and_clear(&ptr);
Note that this passes a pointer to the variable ptr, instead of the value of ptr. So free_and_clear can modify ptr. But this puts some restrictions on how you can use it which don't apply to free - you need a pointer to a modifiable value, rather than just a value.
Upvotes: 6
Reputation: 55415
free() doesn't release memory back to the OS - it releases back to the process's heap manager. For efficiency reasons, it is not zero'd out.
When a process allocates virtual memory, most OS's will hand it a zero'd page. This prevents memory from "leaking" from one process to the other and causing a security issue like you mention.
If you have data in your process that you don't want to keep in memory (for example, a user's password), you are responsible for zeroing it out. Windows provides the SecureZeroMemory API for this.
Upvotes: 12
Reputation: 76510
If I understand the question correctly the OP wants to not leave sensitive information "out there" in fear of it being compromised. As the previous posters pointed out freeing the memory before releasing it is the answer to wiping the data.
However, it is far from the answer to what the OP is trying to achieve. For starters zeroing the memory is 100% useless in securing your application. Even if the memory page is allocated to another running process, in most OSs this procedure is non-deterministic and no sane hacker will EVER use such a technique to compromise your data.
What a sane hacker would do is whack your program into a disassembler and debug through it until they figure out where the data is and then use it. Since a call to memset is bleedingly obvious once you are a competent disassemblerator(yes, disassemblerator :) ) our hypothetical hacker would just get to the data before memset happens.
To really answer your question. If you are trying to protect some sensitive data inside your C program you are getting in the domain that is far beyond normal C/C++ programmers(like myself) into realm of writing virtual machines for executing your data sensitive operations.
The fact that you even ask this question means that it would be reckless for you to develop something that requires this level of protection. Also it will absolutely not be the first stop in protecting your data. Pick the low hanging fruit first and there is plenty info on the web about that.
Upvotes: 2
Reputation: 170499
Zeroing out the memory block when freeing it will require extra time. Since most of time there's actually no need in it it is not done by default.
If you really need (say you used memory for storing a password or a cryptographic key) - call memset()
before freeing the block. Writing an utility function that chains memset()
and free()
is not a problem either.
Upvotes: 29
Reputation: 202505
The original C philosophy was to have keep implicit effects to an absolute minimum. If a programmer wants a pointer zeroed after the memory pointed to is freed, that's what the programmer should write. Those of us who do often use a macro like this one:
#define FREE(P) ((void)(free((P)), (P) = NULL))
Of course if the expression passed to FREE
has side effects, one has just opened a large can of worms...
Upvotes: 6
Reputation: 1213
Setting the result of a freed pointer to zero may seem to be bullshit, but if the pointer is inadvertently accessed later, you'll get a segfault (at least in a real OS), and the debugger will point to where this abomination is happening. But as others have noted, when you call "free" later, all free has is the address to free, and nothing else.
Upvotes: 2
Reputation: 1378
A very specific answer to the question "Why is the memory not set to 0 after freeing it?" is "Because the language specification does not define that behavior.
From the draft ANSI C spec: "The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation."
Upvotes: 2
Reputation: 479
Once you free memory using free(), the value & the memory allocated at that particular address gets deleted (freed) but the pointer still points to that address. If you try to de-reference that pointer you will get Segmentation fault or Bus error. So, its safe to assign NULL value to the pointer once the memory pointed by the pointer is freed. You may refer < Setting variable to NULL after free >
Upvotes: 0
Reputation: 127448
If you want the memory to be set to 0 when you free it, you'll have to do it yourself before you free()
it. If you try after you free()
it there are no guarantees that it hasn't been allocated again. For instance you can use memset()
for that.
free()
doesn't guarantee that the memory will be cleared because C doesn't guarantee that malloc()
will return initialized memory. Either way you have to initialize it yourself after it's been allocated, so there's no point in clearing it when it's free()
'd
Upvotes: 6
Reputation: 1316
C why is the memory not explictly set to zero in the free implementation .
Because of speed.
Because after we free the memory any how we set it to zero after freeing.
Eh?
Upvotes: 7