summaryrefslogtreecommitdiff
path: root/src/libnm-systemd-shared/src/basic/tmpfile-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-systemd-shared/src/basic/tmpfile-util.c')
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.c94
1 files changed, 78 insertions, 16 deletions
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
index d6bafa5d7d..a66ee82d7e 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
@@ -16,8 +16,10 @@
#include "path-util.h"
#include "process-util.h"
#include "random-util.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
+#include "sync-util.h"
#include "tmpfile-util.h"
#include "umask-util.h"
@@ -276,7 +278,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
return fd;
}
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
_cleanup_free_ char *tmp = NULL;
int r, fd;
@@ -290,7 +292,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
* which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
* "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
- fd = open_parent(target, O_TMPFILE|flags, 0640);
+ fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
if (fd >= 0) {
*ret_path = NULL;
return fd;
@@ -302,7 +304,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
if (r < 0)
return r;
- fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+ fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
if (fd < 0)
return -errno;
@@ -333,24 +335,84 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE
return 0;
}
-int link_tmpfile(int fd, const char *path, const char *target) {
+static int link_fd(int fd, int newdirfd, const char *newpath) {
+ int r;
+
+ assert(fd >= 0);
+ assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+ assert(newpath);
+
+ /* Try symlinking via /proc/fd/ first. */
+ r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
+ if (r != -ENOENT)
+ return r;
+
+ /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
+ * more recent kernel, but does not require /proc/ mounted) */
+ if (proc_mounted() != 0)
+ return r;
+
+ return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
+}
+
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) {
+ _cleanup_free_ char *tmp = NULL;
+ int r;
+
assert(fd >= 0);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(target);
- /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
- * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
- * on the directory, and renameat2() is used instead.
- *
- * Note that in both cases we will not replace existing files. This is because linkat() does not support this
- * operation currently (renameat2() does), and there is no nice way to emulate this. */
+ /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
+ * an fd created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE
+ * is not supported on the directory, and renameat2() is used instead. */
- if (path)
- return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+ if (FLAGS_SET(flags, LINK_TMPFILE_SYNC) && fsync(fd) < 0)
+ return -errno;
- return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW));
+ if (path) {
+ if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+ r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
+ else
+ r = rename_noreplace(dir_fd, path, dir_fd, target);
+ if (r < 0)
+ return r;
+ } else {
+
+ r = link_fd(fd, dir_fd, target);
+ if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+ return r;
+
+ /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's
+ * linkat() logic does not allow that. We work-around this by linking the file to a random name
+ * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is
+ * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file
+ * system under a temporary name) is very short. */
+
+ r = tempfn_random(target, NULL, &tmp);
+ if (r < 0)
+ return r;
+
+ if (link_fd(fd, dir_fd, tmp) < 0)
+ return -EEXIST; /* propagate original error */
+
+ r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
+ if (r < 0) {
+ (void) unlinkat(dir_fd, tmp, 0);
+ return r;
+ }
+ }
+
+ if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
+ r = fsync_full(fd);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
}
-int flink_tmpfile(FILE *f, const char *path, const char *target) {
+int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags) {
int fd, r;
assert(f);
@@ -360,11 +422,11 @@ int flink_tmpfile(FILE *f, const char *path, const char *target) {
if (fd < 0) /* Not all FILE* objects encapsulate fds */
return -EBADF;
- r = fflush_sync_and_check(f);
+ r = fflush_and_check(f);
if (r < 0)
return r;
- return link_tmpfile(fd, path, target);
+ return link_tmpfile(fd, path, target, flags);
}
int mkdtemp_malloc(const char *template, char **ret) {