]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31754 from YHNdnzj/journal-fd-namespace
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 14 Mar 2024 10:59:19 +0000 (19:59 +0900)
committerGitHub <noreply@github.com>
Thu, 14 Mar 2024 10:59:19 +0000 (19:59 +0900)
journal/cat: allow connecting output to specific journal namespace

13 files changed:
TODO
man/systemd-soft-reboot.service.xml
meson.build
src/basic/fs-util.c
src/basic/fs-util.h
src/basic/path-util.c
src/basic/path-util.h
src/basic/stat-util.c
src/basic/tmpfile-util.c
src/shared/creds-util.c
src/shared/ptyfwd.c
src/test/test-fs-util.c
src/test/test-path-util.c

diff --git a/TODO b/TODO
index 5a1f553ff0bcce90dbde4495ebfc05d53dfd9270..5fd4fdcf69cd9e98059a7267a949c0c997ca6983 100644 (file)
--- a/TODO
+++ b/TODO
@@ -132,7 +132,7 @@ Features:
 
 * move documentation about our common env vars (SYSTEMD_LOG_LEVEL,
   SYSTEMD_PAGER, …) into a man page of its own, and just link it from our
-  various man pages that so far embedd the whole list again and again, in an
+  various man pages that so far embed the whole list again and again, in an
   attempt to reduce clutter and noise a bid.
 
 * vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at
index 138c919ee73a9181a15141e8c602b339bc420e95..354a24679a246943fcef23f4e77500bf4a2961d4 100644 (file)
       also have to be configured manually for correct ordering and conflicting. For example:</para>
 
       <programlisting>[Unit]
-Description=My surviving service
+Description=My Surviving Service
 SurviveFinalKillSignal=yes
 IgnoreOnIsolate=yes
 DefaultDependencies=no
 After=basic.target
-Conflicts=reboot.target
-Before=reboot.target
-Conflicts=kexec.target
-Before=kexec.target
-Conflicts=poweroff.target
-Before=poweroff.target
-Conflicts=halt.target
-Before=halt.target
-Conflicts=rescue.target
-Before=rescue.target
-Conflicts=emergency.target
-Before=emergency.target
+Conflicts=reboot.target kexec.target poweroff.target halt.target rescue.target emergency.target
+Before=reboot.target kexec.target poweroff.target halt.target rescue.target emergency.target
 
 [Service]
 Type=oneshot
-ExecStart=sleep infinity
-      </programlisting>
+ExecStart=sleep infinity</programlisting>
       </listitem>
 
       <listitem><para>File system mounts may remain mounted during the transition, and complex storage
index 7ac331fb36ae509c63fda40a40a40cb58fe39568..078ba822ea15dbf259d3e48ee7af01cfae6b23c1 100644 (file)
@@ -1853,20 +1853,15 @@ endif
 conf.set_quoted('VERSION_TAG', version_tag)
 
 vcs_tag = get_option('vcs-tag')
-if vcs_tag and fs.is_dir(project_source_root / '.git')
-        version_h = vcs_tag(
-                input : 'src/version/version.h.in',
-                output : 'version.h',
-                fallback : '',
-                command : ['sh', '-c', 'echo "-g$(git -C . describe --abbrev=7 --match="" --always --dirty=^)"'],
-        )
-else
-        version_h = configure_file(
-                input : 'src/version/version.h.in',
-                output : 'version.h',
-                configuration : configuration_data({'VCS_TAG' : ''}),
-        )
-endif
+command = ['sh', '-c',
+           vcs_tag and fs.exists(project_source_root / '.git') ?
+                   'echo "-g$(git -C . describe --abbrev=7 --match="" --always --dirty=^)"' : ':']
+version_h = vcs_tag(
+        input : 'src/version/version.h.in',
+        output : 'version.h',
+        fallback : '',
+        command : command,
+)
 
 shared_lib_tag = get_option('shared-lib-tag')
 if shared_lib_tag == ''
index 5bc7d2f95beb568c89c7dafe0d24c21739c9d3ba..7f0b5814be540189366bba72c97b5c496674c2da 100644 (file)
@@ -1236,3 +1236,99 @@ int xopenat_lock_full(
 
         return TAKE_FD(fd);
 }
+
+int link_fd(int fd, int newdirfd, const char *newpath) {
+        int r;
+
+        assert(fd >= 0);
+        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+        assert(newpath);
+
+        /* Try linking via /proc/self/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 linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+        _cleanup_close_ int old_fd = -EBADF;
+        int r;
+
+        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
+        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
+
+        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
+         * same inode. */
+
+        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
+                return -EISDIR;
+
+        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
+                return -EISDIR;
+
+        if (path_implies_directory(newpath))
+                return -EISDIR;
+
+        /* First, try to link this directly */
+        if (oldpath)
+                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
+        else
+                r = link_fd(olddirfd, newdirfd, newpath);
+        if (r >= 0)
+                return 0;
+        if (r != -EEXIST)
+                return r;
+
+        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
+        if (old_fd < 0)
+                return old_fd;
+
+        struct stat old_st;
+        if (fstat(old_fd, &old_st) < 0)
+                return -errno;
+
+        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
+                return -EISDIR;
+
+        struct stat new_st;
+        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
+                return -errno;
+
+        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
+                return -EEXIST;
+
+        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
+                return 0;
+
+        _cleanup_free_ char *tmp_path = NULL;
+        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
+        if (r < 0)
+                return r;
+
+        r = link_fd(old_fd, newdirfd, tmp_path);
+        if (r < 0) {
+                if (!ERRNO_IS_PRIVILEGE(r))
+                        return r;
+
+                /* If that didn't work due to permissions then go via the path of the dentry */
+                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
+                if (r < 0)
+                        return r;
+        }
+
+        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
+        if (r < 0) {
+                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
+                return r;
+        }
+
+        return 0;
+}
index 6a1e2e76d14929fc246d988e511c33d4ddd45b38..58a7d0a74595cfc3fd02ecefca5a80748688b29d 100644 (file)
@@ -146,3 +146,7 @@ int xopenat_lock_full(int dir_fd, const char *path, int open_flags, XOpenFlags x
 static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) {
         return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation);
 }
+
+int link_fd(int fd, int newdirfd, const char *newpath);
+
+int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
index 6810bf66aa32f8b7459fc8ab0c8f96fd4bf3e638..05a21f8f8e271deab4a9f693d9ab478bb64a8383 100644 (file)
@@ -1336,6 +1336,20 @@ bool dot_or_dot_dot(const char *path) {
         return path[2] == 0;
 }
 
+bool path_implies_directory(const char *path) {
+
+        /* Sometimes, if we look at a path we already know it must refer to a directory, because it is
+         * suffixed with a slash, or its last component is "." or ".." */
+
+        if (!path)
+                return false;
+
+        if (dot_or_dot_dot(path))
+                return true;
+
+        return ENDSWITH_SET(path, "/", "/.", "/..");
+}
+
 bool empty_or_root(const char *path) {
 
         /* For operations relative to some root directory, returns true if the specified root directory is
index cf001e3c0c0f824a8f93b4d55af46cd7155d5f01..47699e6414210e7efb2f9464068019664f5beda3 100644 (file)
@@ -205,6 +205,8 @@ bool valid_device_allow_pattern(const char *path);
 
 bool dot_or_dot_dot(const char *path);
 
+bool path_implies_directory(const char *path);
+
 static inline const char *skip_dev_prefix(const char *p) {
         const char *e;
 
index ab45eda0edcc8747cda98df27c321aa4b2817d4b..4040b172268f8b02f7a506a843de2877dc6c81d0 100644 (file)
@@ -535,7 +535,7 @@ const char* inode_type_to_string(mode_t m) {
                 return "sock";
         }
 
-        /* Note anonmyous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
+        /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
          * return an .st_mode where we'll return NULL here! */
         return NULL;
 }
index e77ca9424892d7a113b258c388a04edabfa9282e..3a3f7dcc0ff1c7b1e106b3b26d309a234a92c6b9 100644 (file)
@@ -330,28 +330,7 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE
         return 0;
 }
 
-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);
@@ -370,33 +349,14 @@ int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, Li
                         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_REPLACE))
+                        r = linkat_replace(fd, /* oldpath= */ NULL, dir_fd, target);
+                else
+                        r = link_fd(fd, dir_fd, target);
         }
+        if (r < 0)
+                return r;
 
         if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
                 r = fsync_full(fd);
index 2d5846a06ffe662d1fb1a6e88d76463c4c6bd3cd..ba8aee335541aab8add01185327f622b6b5bc8a8 100644 (file)
@@ -28,6 +28,7 @@
 #include "random-util.h"
 #include "sparse-endian.h"
 #include "stat-util.h"
+#include "tmpfile-util.h"
 #include "tpm2-util.h"
 #include "user-util.h"
 #include "varlink.h"
@@ -377,22 +378,9 @@ static int make_credential_host_secret(
         assert(dfd >= 0);
         assert(fn);
 
-        /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
-         * linkat(2) step at the end.  The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
-         * capability when it uses the AT_EMPTY_PATH flag. */
-        if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
-                fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
-                if (fd < 0)
-                        log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
-        }
-        if (fd < 0) {
-                if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
-                        return -ENOMEM;
-
-                fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
-                if (fd < 0)
-                        return -errno;
-        }
+        fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t);
+        if (fd < 0)
+                return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m");
 
         r = chattr_secret(fd, 0);
         if (r < 0)
@@ -412,26 +400,21 @@ static int make_credential_host_secret(
         if (r < 0)
                 goto fail;
 
-        if (fsync(fd) < 0) {
+        if (fchmod(fd, 0400) < 0) {
                 r = -errno;
                 goto fail;
         }
 
-        warn_not_encrypted(fd, flags, dirname, fn);
-
-        if (t) {
-                r = rename_noreplace(dfd, t, dfd, fn);
-                if (r < 0)
-                        goto fail;
-
-                t = mfree(t);
-        } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
+        if (fsync(fd) < 0) {
                 r = -errno;
                 goto fail;
         }
 
-        if (fsync(dfd) < 0) {
-                r = -errno;
+        warn_not_encrypted(fd, flags, dirname, fn);
+
+        r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to link host key into place: %m");
                 goto fail;
         }
 
@@ -439,10 +422,8 @@ static int make_credential_host_secret(
                 void *copy;
 
                 copy = memdup(buf.data, sizeof(buf.data));
-                if (!copy) {
-                        r = -ENOMEM;
-                        goto fail;
-                }
+                if (!copy)
+                        return -ENOMEM;
 
                 *ret = IOVEC_MAKE(copy, sizeof(buf.data));
         }
index 11997130540a071ce8ecb4e4a56941568d03290c..a514428ef2c75d6f484aaa0a0569c956102880de 100644 (file)
@@ -295,7 +295,7 @@ static int insert_carriage_return_color(PTYForward *f, size_t offset) {
         if (!f->background_color)
                 return 0;
 
-        /* When we see a carriage return (ASCII 13) this this sets only the background */
+        /* When we see a carriage return (ASCII 13) then this sets only the background */
 
         s = background_color_sequence(f);
         if (!s)
index b32feffd30333f19f3c2ad3cdd7af0316d6b8b88..b27c79fdc93d1ac8ae69ed22abb05e10d1bd6428 100644 (file)
@@ -753,6 +753,36 @@ TEST(xopenat_lock_full) {
         assert_se(xopenat_lock_full(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
 }
 
+TEST(linkat_replace) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        _cleanup_close_ int fd1 = openat(tfd, "foo", O_CREAT|O_RDWR|O_CLOEXEC, 0600);
+        assert_se(fd1 >= 0);
+
+        assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0);
+        assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0);
+
+        _cleanup_close_ int fd1_check = openat(tfd, "bar", O_RDWR|O_CLOEXEC);
+        assert_se(fd1_check >= 0);
+
+        assert_se(inode_same_at(fd1, NULL, fd1_check, NULL, AT_EMPTY_PATH) > 0);
+
+        _cleanup_close_ int fd2 = openat(tfd, "baz", O_CREAT|O_RDWR|O_CLOEXEC, 0600);
+        assert_se(fd2 >= 0);
+
+        assert_se(inode_same_at(fd1, NULL, fd2, NULL, AT_EMPTY_PATH) == 0);
+
+        assert_se(linkat_replace(tfd, "foo", tfd, "baz") >= 0);
+
+        _cleanup_close_ int fd2_check = openat(tfd, "baz", O_RDWR|O_CLOEXEC);
+
+        assert_se(inode_same_at(fd2, NULL, fd2_check, NULL, AT_EMPTY_PATH) == 0);
+        assert_se(inode_same_at(fd1, NULL, fd2_check, NULL, AT_EMPTY_PATH) > 0);
+}
+
 static int intro(void) {
         arg_test_dir = saved_argv[1];
         return EXIT_SUCCESS;
index f5a425689a3f0a9481484e6916e481d0ea47a717..ca11bf3a29c854073bc031f1e9dd9d3c9fde252e 100644 (file)
@@ -1305,4 +1305,28 @@ TEST(print_MAX) {
         assert_cc(FILENAME_MAX == PATH_MAX);
 }
 
+TEST(path_implies_directory) {
+        assert_se(!path_implies_directory(NULL));
+        assert_se(!path_implies_directory(""));
+        assert_se(path_implies_directory("/"));
+        assert_se(path_implies_directory("////"));
+        assert_se(path_implies_directory("////.///"));
+        assert_se(path_implies_directory("////./"));
+        assert_se(path_implies_directory("////."));
+        assert_se(path_implies_directory("."));
+        assert_se(path_implies_directory("./"));
+        assert_se(path_implies_directory("/."));
+        assert_se(path_implies_directory(".."));
+        assert_se(path_implies_directory("../"));
+        assert_se(path_implies_directory("/.."));
+        assert_se(!path_implies_directory("a"));
+        assert_se(!path_implies_directory("ab"));
+        assert_se(path_implies_directory("ab/"));
+        assert_se(!path_implies_directory("ab/a"));
+        assert_se(path_implies_directory("ab/a/"));
+        assert_se(path_implies_directory("ab/a/.."));
+        assert_se(path_implies_directory("ab/a/."));
+        assert_se(path_implies_directory("ab/a//"));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);