13

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...

Shog9
  • 156,901
  • 35
  • 231
  • 235
  • 3
    You might want to type a little more (see the "edit" button?). Explain what a little more exactly what you mean, *without* assuming that we understand where you are coming from. Code samples might help. Explaining *why* you want to accomplish this might help. – dmckee --- ex-moderator kitten Aug 18 '09 at 01:36
  • 1
    Well, i'm tempted to just close this and move on, but on the off-chance that he actually meant to ask what he *did* ask... and that sharptooth's guess is correct, i've edited the question to better match. May God have mercy on my soul... – Shog9 Aug 20 '09 at 22:24
  • I don't see any reason to close it. He's obviously quite confused, whichever question he's asking, and there are good answers for both of them. It would be nice if he came back and clarified, but I somehow doubt that's going to happen. – Chris Lutz Aug 20 '09 at 22:37

17 Answers17

28

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.

Shog9
  • 156,901
  • 35
  • 231
  • 235
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 20
    Actually, memset isn't always enough. memset is typically a compiler intrinsic and can be removed if the compiler determine that you won't use the data again (this probably won't happen for a call to free() but is entirely possible for a buffer on the stack). Windows provides the SecureZeroMemory function which won't be optimized away. More info here: https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data – Michael Aug 20 '09 at 23:07
10

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.

Michael
  • 54,279
  • 5
  • 125
  • 144
  • I'd replace *For efficiency reasons, it is not zero-d out* by *Zero-ing out after free introduces a performance overhead, but some heap management implementations do so for security reasons or as a result of storing heap metadata*. – Nino Filiu Jul 03 '19 at 15:13
7

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?

maykeye
  • 1,286
  • 1
  • 12
  • 16
6

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

Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319
  • I was recently asked in a code review: how do we know the compiler won't optimize the memset() away, knowing that you won't be accessing the memory after the free? – Edward Falk Oct 29 '18 at 21:24
6
memset(ptr, 0, size);
free(ptr);

i think you want this...

fnurglewitz
  • 2,097
  • 14
  • 21
  • 1
    What is the point of setting memory to 0 and then freeing it? – Avi Aug 17 '09 at 10:24
  • 11
    It can be useful for buffers used to store passwords and cryptographic keys http://stackoverflow.com/questions/786093/does-using-securezeromemory-really-help-to-make-the-application-more-secure – sharptooth Aug 17 '09 at 10:28
6

[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.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 1
    You could write a macro: `#define FREE(x) do { free(x); x = NULL; } while(0);` That way, you could call it without the `&` operator. – Chris Lutz Aug 20 '09 at 22:24
  • 1
    True, although I'm scared of macros which evaluate their argument twice (because I'm scared that one day I'll use them without thinking how many times they do it). In this case the argument has to be an lvalue, which reduces the chance of it having side-effects, but still. You can probably workaround with a temp variable, but once a macro has temp variables I'm always thinking "please, there must be some way to make this a static inline function". I don't think asking the caller to take a pointer is all that bad. – Steve Jessop Sep 03 '09 at 14:02
  • 1
    @Chris Lutz: Quite related: http://stackoverflow.com/questions/1265666/reason-why-not-to-have-a-delete-macro-for-c – moala Mar 08 '12 at 13:09
6

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...

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
  • Can you please give an example of side effects for us mere mortals? – mLstudent33 Jan 31 '20 at 03:42
  • For the macro definition, setting the pointer to `NULL` after freeing it doesn't address the OP's security concerns. The memory that the pointer was pointing to still contains the original data, even though the memory is now available to be re-allocated and no longer has a pointer pointing to it. – 6equj5 Jan 07 '23 at 20:56
3

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.

David Thornley
  • 56,304
  • 9
  • 91
  • 158
2

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."

peterb
  • 1,360
  • 9
  • 14
2

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.

xcramps
  • 1,203
  • 1
  • 9
  • 9
2

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.

Igor Zevaka
  • 74,528
  • 26
  • 112
  • 128
  • 1
    You should explicitly zero-out memory for high-risk data such as passwords, credit card numbers, and so on, as soon as they aren't needed. While this can't prevent all attempts from stealing the data, it at least shrinks the window where it could be stolen. – Michael Aug 20 '09 at 23:04
  • 1
    I don't really agree with that. You are getting into the area where you are second guessing the compiler and the OS. If you want to do that, that's fine, but you really really MUST know what you are doing. For starters it's highly likely that the compiler optimizes the memset call. Secondly, as I said before, it's pretty useless as, unless you obfuscate, the hacker will just get in when he knows the data is going to be available. It is far from providing any meaningful protection and it's likely your app has a thousand other security deficiencies that you should fix first. – Igor Zevaka Aug 20 '09 at 23:25
  • 1
    One should do a secure_memset (that isn't subject to being optimized away) in particular on multi-user systems. That's because the memory that gets `free`d may be returned by a future call to malloc by another (OS) user. May be unlikely, but could be exploited. – Inshallah Aug 21 '09 at 00:22
1

"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.

https://www.usenix.org/sites/default/files/conference/protected-files/usenixsecurity17_slides_zhaomo_yang.pdf

// 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_
angstyloop
  • 117
  • 6
0

Because it would be a pure waste of time.

cedrou
  • 2,780
  • 1
  • 18
  • 23
0

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 >

Community
  • 1
  • 1
Naveen
  • 439
  • 1
  • 6
  • 15
0

There's also bzero(3).

  • From `man bzero`: "CONFORMING TO The bzero() function is deprecated (marked as LEGACY in POSIX.1-2001); use memset(3) in new programs. POSIX.1-2008 removes the specification of bzero(). The bzero() function first appeared in 4.3BSD. The explicit_bzero() function is a nonstandard extension that is also present on some of the BSDs. Some other implementations have a similar function, such as memset_explicit() or memset_s()." – hellork Nov 22 '18 at 21:46
0

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);                                \
})                                          \
Locoder
  • 22
  • 1
  • 4
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31836638) – Alex Guteniev May 27 '22 at 15:07
  • Imo this answer is explicit enough @AlexGuteniev – Locoder May 29 '22 at 16:00
0

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).

chqrlie
  • 131,814
  • 10
  • 121
  • 189