...
There is not much that can be programmatically done to ensure the file removed is the same file that was opened, processed, and closed except to make sure that the file is opened in a secure directory with privileges that would prevent the file from being manipulated by an untrusted user.
Non-Compliant Code Example
This code example attempts to save a secret in a file specified by path. While path is assumed to come from a trusted source (see FIO02-A. Canonicalize path names originating from untrusted sources), this does not imply that an attacker will not be able to read the secret after it is written out to file.
| Code Block | ||
|---|---|---|
| ||
void write_secret(const char* path, secret_t my_secret) {
/* save my_secret to the file specified by path */
}
|
Compliant Solution (POSIX)
...
| Code Block | ||
|---|---|---|
| ||
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>
#include <sys/stat.h>
#include <string.h>
/* Returns nonzero if directory is secure, zero otherwise */
int secure_dir(const char* path) {
char *realpath_res = realpath(path, NULL);
char *path_copy = NULL;
char *dirname_res = NULL;
char ** dirs = NULL;
int num_of_dirs = 0;
int secure = 1;
int i;
struct stat buf;
uid_t my_uid = geteuid();
if (realpath_res == NULL) {
/* Handle Error */
}
if (!(path_copy = strdup(realpath_res))) {
/* Handle Error */
}
dirname_res = path_copy;
/* Figure out how far it is to the root */
while (1) {
dirname_res = dirname(dirname_res);
num_of_dirs++;
if ((strcmp(dirname_res, ".") == 0) ||
(strcmp(dirname_res, "/") == 0)) {
break;
}
}
free(path_copy);
path_copy = NULL;
/* Now allocate and fill the dirs array */
if (!(dirs = (char **)malloc(num_of_dirs*sizeof(*dirs)))) {
/* Handle Error */
}
if (!(dirs[num_of_dirs - 1] = strdup(realpath_res))) {
/* Handle Error */
}
if(!(path_copy = strdup(realpath_res))) {
/* Handle Error */
}
dirname_res = path_copy;
for (i = 1; i < num_of_dirs; i++) {
dirname_res = dirname(dirname_res);
dirs[num_of_dirs - i - 1] = strdup(dirname_res);
}
free(path_copy);
path_copy = NULL;
/* Traverse from the root to the top, checking
* permissions along the way */
for (i = 0; i < num_of_dirs; i++) {
if (stat(dirs[i], &buf) != 0) {
/* Handle Error */
}
if ((buf.st_uid != my_uid) && (buf.st_uid != 0)) {
/* Directory is owned by someone besides user or root */
secure = 0;
} else if (!(buf.st_mode & S_ISVTX) &&
(buf.st_mode & (S_IWGRP | S_IWOTH))) {
/* Others have permission to rename or remove files here */
secure = 0;
}
free(dirs[i]);
dirs[i] = NULL;
}
free(dirs);
dirs = NULL;
return secure;
}
|
This compliant solution uses the secure_dir() function above to verify that my_secret is written to a secure file.
| Code Block | ||
|---|---|---|
| ||
void write_secret(const char* path, secret_t my_secret) {
if (!secure_dir(path)) {
/* Handle Error */
}
/* save my_secret to the file specified by path */
}
|
This compliant solution uses the secure_dir() function above to ensure that the file that is opened and written to is the intended file, and also the file that is eventually removed.
| Code Block | ||
|---|---|---|
| ||
char *file_name;
FILE *f_ptr;
/* initialize file_name */
if (!secure_dir(path)) {
/* Handle Error */
}
f_ptr = fopen(file_name, "w");
if (f_ptr == NULL) {
/* Handle Error */
}
/*... Process file ...*/
if (fclose(f_ptr) != 0) {
/* Handle Error */
}
if (remove(file_name) != 0) {
/* Handle Error */
}
|
...
Risk Assessment
Failing to ensure proper permissions in a directory may lead to sensitive data getting saved to (or critical configuration or other input files being read from) public directories to which an attacker has access.
...