Many functions require the allocation of multiple resources. Failing and returning somewhere in the middle of this function without freeing all of the allocated resources could produce a memory leak. It is a common error to forget to free one (or all) of the resources in this manner, so a goto
- chain is the simplest and cleanest way to organize exits while preserving the order of freed resources.
Noncompliant Code Example (POSIX)
In this noncompliant example, exit code is written for every instance in which the function can terminate prematurely. Notice how failing to close fin2
produces a resource leak, leaving an open file descriptor.unmigrated-wiki-markup
_Please note that these examples assume {{errno_t
}} and {{NOERR
}} to be defined, as requested by \[[DCL09-C. Declare functions that return an errno error code with a return type of errno_t|as recommended in DCL09-C. Declare functions that return errno with a return type of errno_t]\]. An equivalent compatible example would define {{errno_t
}} as an {{int
}} and {{NOERR
}} as zero._zero.
These examples also assume that errno
is set if fopen()
or malloc()
fail. These are guaranteed by POSIX but not by C11. See ERR30-C. Take care when reading errno for more details.
Code Block | ||||
---|---|---|---|---|
| ||||
typedef struct object { //* A genericGeneric struct -- The: contents don't matter */ int propertyA, propertyB, propertyC; } object_t; errno_t do_something(void){ FILE *fin1, *fin2; object_t *obj; errno_t ret_val; fin1 = fopen("some_file", "r"); if (fin1 == NULL) { return errno; } fin2 = fopen("some_other_file", "r"); if (fin2 == NULL) { fclose(fin1); return errno; } obj = malloc(sizeof(object_t)); if (obj == NULL) { ret_val = errno; fclose(fin1); return ret_val; //* forgotForgot to close fin2 !! */ } //* ... moreMore code ... */ fclose(fin1); fclose(fin2); free(obj); return NOERR; } |
This is also just a small example; in much larger examples, errors like this would be are even harder to detect.
Compliant Solution (POSIX, Nested Ifs)
This compliant solution uses nested if statements to properly close files and free memory in the case that any error occurs. When the number of resources to manage is small (3 in this example), nested if statements will be simpler than a goto chain.
Code Block | ||||
---|---|---|---|---|
| ||||
/* ... Assume the same struct used previously ... */
errno_t do_something(void) {
FILE *fin1, *fin2;
object_t *obj;
errno_t ret_val = NOERR; /* Initially assume a successful return value */
if ((fin1 = fopen("some_file", "r")) != NULL) {
if ((fin2 = fopen("some_other_file", "r")) != NULL) {
if ((obj = malloc(sizeof(object_t))) != NULL) {
/* ... More code ... */
/* Clean-up & handle errors */
free(obj);
} else {
ret_val = errno;
}
fclose(fin2);
} else {
ret_val = errno;
}
fclose(fin1);
} else {
ret_val = errno;
}
return ret_val;
} |
Compliant Solution (POSIX, Goto Chain)
Occasionally, the number of resources to manage in one function will be too large to permit using nested ifs to manage them.
In this revised version, we have used a goto-chain in replacement of a goto
chain replaces each individual return segment. If there is no error occurs, control flow will fall falls through to the the SUCCESS
label label, release releases all of the resources, and return returns NOERR
. In the case of If an error occurs, the return value will be value is set to to errno
, control flow will jump jumps to the proper failure label, and the appropriate resources will be resources are released before returning.
Code Block | ||||
---|---|---|---|---|
| ||||
//* ... assumeAssume the same struct asused abovepreviously ... */ errno_t do_something(void) { FILE *fin1, *fin2; object_t *obj; errno_t ret_val = NOERR; //* Initially assume a successful return value */ fin1 = fopen("some_file", "r"); if (finfin1 == NULL) { ret_val = errno; goto FAIL_FIN1; } fin2 = fopen("some_other_file", "r"); if (fin2 == NULL) { ret_val = errno; goto FAIL_FIN2; } obj = malloc(sizeof(object_t)); if (obj == NULL) { ret_val = errno; goto FAIL_OBJ; } //* ... moreMore code ... */ SUCCESS: //* Clean up everything */ free(obj); FAIL_OBJ: //* Otherwise, close only the resources we opened */ fclose(fin2); FAIL_FIN2: fclose(fin1); FAIL_FIN1: return ret_val; } |
The benefits of this method are that This method is beneficial because the code is cleaner and we prevent the rewriting of , and the programmer does not need to rewrite similar code upon every function error.
Note that this guideline does not advocate more general uses of goto, which is still considered harmful. The use of goto in these cases is specifically to transfer control within a single function body.
Compliant Solution (copy_process()
from Linux kernel)
Some effective examples of goto
- chains are quite large. The following This compliant code solution is an excerpt from the Linux kernel. This is the copy_process
function from kernel/fork.c
from Version version 2.6.29 of the kernel.
The function uses 17 goto
labels (not all displayed here) in order to perform cleanup code should any internal function yield an error code. If no errors occur, the program returns a pointer to the new process p
. If any error occurs, the program diverts control to a particular goto
label, which performs cleanup for the sections of the function that have currently been successfully executed , while not performing cleanup on sections of the function that have not been executed yet. Consequently but not for sections that have not yet been executed. Consequently, only resources that were successfully opened are actually closed.
All comments in this excerpt were added to indicate additional code in the kernel not displayed here.
Code Block | ||||
---|---|---|---|---|
| ||||
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { int retval; struct task_struct *p; int cgroup_callbacks_done = 0; if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) return ERR_PTR(-EINVAL); /* ... */ retval = security_task_create(clone_flags); if (retval) goto fork_out; retval = -ENOMEM; p = dup_task_struct(current); if (!p) goto fork_out; /* ... */ /* copyCopy all the process information */ if ((retval = copy_semundo(clone_flags, p))) goto bad_fork_cleanup_audit; if ((retval = copy_files(clone_flags, p))) goto bad_fork_cleanup_semundo; if ((retval = copy_fs(clone_flags, p))) goto bad_fork_cleanup_files; if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; if ((retval = copy_namespaces(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_io(clone_flags, p))) goto bad_fork_cleanup_namespaces; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_io; /* ... */ return p; /* ... cleanupCleanup code starts here ... */ bad_fork_cleanup_io: put_io_context(p->io_context); bad_fork_cleanup_namespaces: exit_task_namespaces(p); bad_fork_cleanup_mm: if (p->mm) mmput(p->mm); bad_fork_cleanup_signal: cleanup_signal(p); bad_fork_cleanup_sighand: __cleanup_sighand(p->sighand); bad_fork_cleanup_fs: exit_fs(p); /* blockingBlocking */ bad_fork_cleanup_files: exit_files(p); /* blockingBlocking */ bad_fork_cleanup_semundo: exit_sem(p); bad_fork_cleanup_audit: audit_free(p); /* ... moreMore cleanup code ... */ fork_out: return ERR_PTR(retval); } |
Risk Assessment
Failure to free allocated memory or close opened files results in a memory leak and possibly unexpected results.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MEM12-C |
Low |
Probable |
Medium |
P4 | L3 |
References
Wiki Markup |
---|
\[[ISO/IEC 9899:1999|AA. References#ISO/IEC 9899-1999]\] Section 7.20.3, "Memory management functions"
\[[ISO/IEC 9899:1999|AA. References#ISO/IEC 9899-1999]\] Section 7.19.5, "File access functions"
[Linux kernel Sourcecode (v2.6.xx)|http://www.kernel.org/pub/linux/kernel/v2.6] 2.6.29, {{kernel/fork.c}}, the {{copy_process()}} function
\[[Seacord 05|AA. References#Seacord 05]\] Chapter 4, "Dynamic Memory Management" |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Klocwork |
| MLK.MIGHT MLK.MUST MLK.RET.MIGHT MLK.RET.MUST RH.LEAK | |||||||
LDRA tool suite |
| 50 D | Partially implemented | ||||||
Parasoft C/C++test |
| CERT_C-MEM12-a | Ensure resources are freed | ||||||
PC-lint Plus |
| 429 | Assistance provided | ||||||
Polyspace Bug Finder |
| Checks for memory leak and resource leak (rec. partially covered) |
Bibliography
Dijkstra, Edgar, "Go To Statement Considered Harmful.", 1968 | |
Linux Kernel Sourcecode (v2.6.xx) | 2.6.29, kernel/fork.c , the copy_process() Function |
[Seacord 2013] | Chapter 4, "Dynamic Memory Management" |
...