util-lib: rework /tmp and /var/tmp handling code

Beef up the existing var_tmp() call, rename it to var_tmp_dir() and add a
matching tmp_dir() call (the former looks for the place for /var/tmp, the
latter for /tmp).

Both calls check $TMPDIR, $TEMP, $TMP, following the algorithm Python3 uses.
All dirs are validated before use. secure_getenv() is used in order to limite
exposure in suid binaries.

This also ports a couple of users over to these new APIs.

The var_tmp() return parameter is changed from an allocated buffer the caller
will own to a const string either pointing into environ[], or into a static
const buffer. Given that environ[] is mostly considered constant (and this is
exposed in the very well-known getenv() call), this should be OK behaviour and
allows us to avoid memory allocations in most cases.

Note that $TMPDIR and friends override both /var/tmp and /tmp usage if set.
This commit is contained in:
Lennart Poettering 2016-07-26 17:23:28 +02:00
parent eb18df724b
commit 992e8f224b
7 changed files with 125 additions and 60 deletions

View File

@ -1161,8 +1161,8 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
char *t, *x; char *t, *x;
uint64_t u; uint64_t u;
unsigned i; unsigned i;
int r;
assert(p);
assert(ret); assert(ret);
/* Turns this: /* Turns this:
@ -1171,6 +1171,12 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
* /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0 * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
*/ */
if (!p) {
r = tmp_dir(&p);
if (r < 0)
return r;
}
if (!extra) if (!extra)
extra = ""; extra = "";
@ -1257,10 +1263,13 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space)
int open_tmpfile_unlinkable(const char *directory, int flags) { int open_tmpfile_unlinkable(const char *directory, int flags) {
char *p; char *p;
int fd; int fd, r;
if (!directory) if (!directory) {
directory = "/tmp"; r = tmp_dir(&directory);
if (r < 0)
return r;
}
/* Returns an unlinked temporary file that cannot be linked into the file system anymore */ /* Returns an unlinked temporary file that cannot be linked into the file system anymore */

View File

@ -496,34 +496,94 @@ int get_files_in_directory(const char *path, char ***list) {
return n; return n;
} }
int var_tmp(char **ret) { static int getenv_tmp_dir(const char **ret_path) {
const char *tmp_dir = NULL; const char *n;
const char *env_tmp_dir = NULL; int r, ret = 0;
char *c = NULL;
int r;
assert(ret); assert(ret_path);
env_tmp_dir = getenv("TMPDIR"); /* We use the same order of environment variables python uses in tempfile.gettempdir():
if (env_tmp_dir != NULL) { * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
r = is_dir(env_tmp_dir, true); FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
if (r < 0 && r != -ENOENT) const char *e;
return r;
if (r > 0) e = secure_getenv(n);
tmp_dir = env_tmp_dir; if (!e)
continue;
if (!path_is_absolute(e)) {
r = -ENOTDIR;
goto next;
}
if (!path_is_safe(e)) {
r = -EPERM;
goto next;
}
r = is_dir(e, true);
if (r < 0)
goto next;
if (r == 0) {
r = -ENOTDIR;
goto next;
}
*ret_path = e;
return 1;
next:
/* Remember first error, to make this more debuggable */
if (ret >= 0)
ret = r;
} }
if (!tmp_dir) if (ret < 0)
tmp_dir = "/var/tmp"; return ret;
c = strdup(tmp_dir); *ret_path = NULL;
if (!c) return ret;
return -ENOMEM; }
*ret = c;
static int tmp_dir_internal(const char *def, const char **ret) {
const char *e;
int r, k;
assert(def);
assert(ret);
r = getenv_tmp_dir(&e);
if (r > 0) {
*ret = e;
return 0;
}
k = is_dir(def, true);
if (k == 0)
k = -ENOTDIR;
if (k < 0)
return r < 0 ? r : k;
*ret = def;
return 0; return 0;
} }
int var_tmp_dir(const char **ret) {
/* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
* even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
* returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
* making it a variable that overrides all temporary file storage locations. */
return tmp_dir_internal("/var/tmp", ret);
}
int tmp_dir(const char **ret) {
/* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
* backed by an in-memory file system: /tmp. */
return tmp_dir_internal("/tmp", ret);
}
int inotify_add_watch_fd(int fd, int what, uint32_t mask) { int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
int r; int r;

View File

@ -61,7 +61,8 @@ int mkfifo_atomic(const char *path, mode_t mode);
int get_files_in_directory(const char *path, char ***list); int get_files_in_directory(const char *path, char ***list);
int var_tmp(char **ret); int tmp_dir(const char **ret);
int var_tmp_dir(const char **ret);
#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) #define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)

View File

@ -30,6 +30,7 @@
#include "compress.h" #include "compress.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "fs-util.h"
#include "journal-internal.h" #include "journal-internal.h"
#include "log.h" #include "log.h"
#include "macro.h" #include "macro.h"
@ -609,7 +610,13 @@ static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
char *temp = NULL; char *temp = NULL;
if (fd < 0) { if (fd < 0) {
temp = strdup("/var/tmp/coredump-XXXXXX"); const char *vt;
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Failed to acquire temporary directory path: %m");
temp = strjoin(vt, "/coredump-XXXXXX", NULL);
if (!temp) if (!temp)
return log_oom(); return log_oom();

View File

@ -826,7 +826,7 @@ int journal_file_verify(
int data_fd = -1, entry_fd = -1, entry_array_fd = -1; int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
unsigned i; unsigned i;
bool found_last = false; bool found_last = false;
_cleanup_free_ char *tmp_dir = NULL; const char *tmp_dir = NULL;
#ifdef HAVE_GCRYPT #ifdef HAVE_GCRYPT
uint64_t last_tag = 0; uint64_t last_tag = 0;
@ -846,7 +846,7 @@ int journal_file_verify(
} else if (f->seal) } else if (f->seal)
return -ENOKEY; return -ENOKEY;
r = var_tmp(&tmp_dir); r = var_tmp_dir(&tmp_dir);
if (r < 0) { if (r < 0) {
log_error_errno(r, "Failed to determine temporary directory: %m"); log_error_errno(r, "Failed to determine temporary directory: %m");
goto fail; goto fail;

View File

@ -954,7 +954,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
/* Create a temporary file we can dump information about deleted images into. We use a temporary file for this /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this
* instead of a pipe or so, since this might grow quit large in theory and we don't want to process this * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this
* continuously */ * continuously */
result_fd = open_tmpfile_unlinkable("/tmp/", O_RDWR|O_CLOEXEC); result_fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC);
if (result_fd < 0) if (result_fd < 0)
return -errno; return -errno;

View File

@ -83,47 +83,35 @@ static void test_get_files_in_directory(void) {
} }
static void test_var_tmp(void) { static void test_var_tmp(void) {
char *tmp_dir = NULL; _cleanup_free_ char *tmpdir_backup = NULL;
char *tmpdir_backup = NULL; const char *tmp_dir = NULL, *t;
const char *default_var_tmp = NULL;
const char *var_name;
bool do_overwrite = true;
default_var_tmp = "/var/tmp"; t = getenv("TMPDIR");
var_name = "TMPDIR"; if (t) {
tmpdir_backup = strdup(t);
if (getenv(var_name) != NULL) { assert_se(tmpdir_backup);
tmpdir_backup = strdup(getenv(var_name));
assert_se(tmpdir_backup != NULL);
} }
unsetenv(var_name); assert(unsetenv("TMPDIR") >= 0);
var_tmp(&tmp_dir); assert_se(var_tmp_dir(&tmp_dir) >= 0);
assert_se(!strcmp(tmp_dir, default_var_tmp)); assert_se(streq(tmp_dir, "/var/tmp"));
free(tmp_dir); assert_se(setenv("TMPDIR", "/tmp", true) >= 0);
assert_se(streq(getenv("TMPDIR"), "/tmp"));
setenv(var_name, "/tmp", do_overwrite); assert_se(var_tmp_dir(&tmp_dir) >= 0);
assert_se(!strcmp(getenv(var_name), "/tmp")); assert_se(streq(tmp_dir, "/tmp"));
var_tmp(&tmp_dir); assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0);
assert_se(!strcmp(tmp_dir, "/tmp")); assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88"));
free(tmp_dir); assert_se(var_tmp_dir(&tmp_dir) >= 0);
assert_se(streq(tmp_dir, "/var/tmp"));
setenv(var_name, "/88_does_not_exist_88", do_overwrite); if (tmpdir_backup) {
assert_se(!strcmp(getenv(var_name), "/88_does_not_exist_88")); assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0);
assert_se(streq(getenv("TMPDIR"), tmpdir_backup));
var_tmp(&tmp_dir);
assert_se(!strcmp(tmp_dir, default_var_tmp));
free(tmp_dir);
if (tmpdir_backup != NULL) {
setenv(var_name, tmpdir_backup, do_overwrite);
assert_se(!strcmp(getenv(var_name), tmpdir_backup));
free(tmpdir_backup);
} }
} }