]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tmpfile-util: truncate original filename if the result filename is too long
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 10 Apr 2022 22:02:52 +0000 (07:02 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 3 Sep 2022 00:47:31 +0000 (09:47 +0900)
This also verify that the extra string does not contain '/'.

src/basic/tmpfile-util.c
src/test/test-tmpfile-util.c

index e0a338c163a726b595ecd17c934c81f39aab4be7..34d3016ba930f1e58e1ae9de987efba2f2b4f1f7 100644 (file)
@@ -86,50 +86,91 @@ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
         return 0;
 }
 
-int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
-        _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
+static int tempfn_build(const char *p, const char *pre, const char *post, bool child, char **ret) {
+        _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL, *result = NULL;
+        size_t len_pre, len_post, len_add;
         int r;
 
+        assert(p);
         assert(ret);
 
         /*
          * Turns this:
          *         /foo/bar/waldo
          *
-         * Into this:
-         *         /foo/bar/.#<extra>waldoXXXXXX
+         * Into this :
+         *         /foo/bar/waldo/.#<pre><post> (child == true)
+         *         /foo/bar/.#<pre>waldo<post> (child == false)
          */
 
-        r = path_extract_directory(p, &d);
-        if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
-                return r;
+        if (pre && strchr(pre, '/'))
+                return -EINVAL;
 
-        r = path_extract_filename(p, &fn);
-        if (r < 0)
-                return r;
+        if (post && strchr(post, '/'))
+                return -EINVAL;
+
+        len_pre = strlen_ptr(pre);
+        len_post = strlen_ptr(post);
+        /* NAME_MAX is counted *without* the trailing NUL byte. */
+        if (len_pre > NAME_MAX - STRLEN(".#") ||
+            len_post > NAME_MAX - STRLEN(".#") - len_pre)
+                return -EINVAL;
+
+        len_add = len_pre + len_post + STRLEN(".#");
+
+        if (child) {
+                d = strdup(p);
+                if (!d)
+                        return -ENOMEM;
+        } else {
+                r = path_extract_directory(p, &d);
+                if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
+                        return r;
 
-        nf = strjoin(".#", strempty(extra), fn, "XXXXXX");
+                r = path_extract_filename(p, &fn);
+                if (r < 0)
+                        return r;
+
+                if (strlen(fn) > NAME_MAX - len_add)
+                        /* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */
+                        fn[NAME_MAX - len_add] = '\0';
+        }
+
+        nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post));
         if (!nf)
                 return -ENOMEM;
 
-        if (!filename_is_valid(nf)) /* New name is not valid? (Maybe because too long?) Refuse. */
-                return -EINVAL;
-
-        if (d)  {
+        if (d) {
                 if (!path_extend(&d, nf))
                         return -ENOMEM;
 
-                *ret = path_simplify(TAKE_PTR(d));
+                result = path_simplify(TAKE_PTR(d));
         } else
-                *ret = TAKE_PTR(nf);
+                result = TAKE_PTR(nf);
+
+        if (!path_is_valid(result)) /* New path is not valid? (Maybe because too long?) Refuse. */
+                return -EINVAL;
 
+        *ret = TAKE_PTR(result);
         return 0;
 }
 
+int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
+        /*
+         * Turns this:
+         *         /foo/bar/waldo
+         *
+         * Into this:
+         *         /foo/bar/.#<extra>waldoXXXXXX
+         */
+
+        return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret);
+}
+
 int tempfn_random(const char *p, const char *extra, char **ret) {
-        _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
-        int r;
+        _cleanup_free_ char *s = NULL;
 
+        assert(p);
         assert(ret);
 
         /*
@@ -140,37 +181,14 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
          *         /foo/bar/.#<extra>waldobaa2a261115984a9
          */
 
-        r = path_extract_directory(p, &d);
-        if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
-                return r;
-
-        r = path_extract_filename(p, &fn);
-        if (r < 0)
-                return r;
-
-        if (asprintf(&nf, ".#%s%s%016" PRIx64,
-                     strempty(extra),
-                     fn,
-                     random_u64()) < 0)
+        if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
                 return -ENOMEM;
 
-        if (!filename_is_valid(nf)) /* Not valid? (maybe because too long now?) — refuse early */
-                return -EINVAL;
-
-        if (d) {
-                if (!path_extend(&d, nf))
-                        return -ENOMEM;
-
-                *ret = path_simplify(TAKE_PTR(d));
-        } else
-                *ret = TAKE_PTR(nf);
-
-        return 0;
+        return tempfn_build(p, extra, s, /* child = */ false, ret);
 }
 
 int tempfn_random_child(const char *p, const char *extra, char **ret) {
-        char *t, *x;
-        uint64_t u;
+        _cleanup_free_ char *s = NULL;
         int r;
 
         assert(ret);
@@ -187,27 +205,10 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
                         return r;
         }
 
-        extra = strempty(extra);
-
-        t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
-        if (!t)
+        if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
                 return -ENOMEM;
 
-        if (isempty(p))
-                x = stpcpy(stpcpy(t, ".#"), extra);
-        else
-                x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
-
-        u = random_u64();
-        for (unsigned i = 0; i < 16; i++) {
-                *(x++) = hexchar(u & 0xF);
-                u >>= 4;
-        }
-
-        *x = 0;
-
-        *ret = path_simplify(t);
-        return 0;
+        return tempfn_build(p, extra, s, /* child = */ true, ret);
 }
 
 int open_tmpfile_unlinkable(const char *directory, int flags) {
index 5e5f60bdc850e15a85f55fe929076619b2b51f22..002bd4616210e2789644848fc2aa6091a267bf36 100644 (file)
@@ -3,6 +3,7 @@
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "log.h"
+#include "path-util.h"
 #include "string-util.h"
 #include "tests.h"
 #include "tmpfile-util.h"
@@ -26,10 +27,13 @@ static void test_tempfn_random_one(const char *p, const char *extra, const char
 }
 
 TEST(tempfn_random) {
+        _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL;
+
         test_tempfn_random_one("", NULL, NULL, -EINVAL);
         test_tempfn_random_one(".", NULL, NULL, -EADDRNOTAVAIL);
         test_tempfn_random_one("..", NULL, NULL, -EINVAL);
         test_tempfn_random_one("/", NULL, NULL, -EADDRNOTAVAIL);
+        test_tempfn_random_one("foo", "hoge/aaa", NULL, -EINVAL);
 
         test_tempfn_random_one("foo", NULL, ".#foo", 0);
         test_tempfn_random_one("foo", "bar", ".#barfoo", 0);
@@ -48,6 +52,36 @@ TEST(tempfn_random) {
         test_tempfn_random_one("./foo/", "bar", ".#barfoo", 0);
         test_tempfn_random_one("../foo/", NULL, "../.#foo", 0);
         test_tempfn_random_one("../foo/", "bar", "../.#barfoo", 0);
+
+        assert_se(dir = new(char, PATH_MAX - 20));
+        memset(dir, 'x', PATH_MAX - 21);
+        dir[PATH_MAX - 21] = '\0';
+        for (size_t i = 0; i < PATH_MAX - 21; i += NAME_MAX + 1)
+                dir[i] = '/';
+
+        assert_se(p = path_join(dir, "a"));
+        assert_se(q = path_join(dir, ".#a"));
+
+        test_tempfn_random_one(p, NULL, q, 0);
+        test_tempfn_random_one(p, "b", NULL, -EINVAL);
+
+        p = mfree(p);
+        q = mfree(q);
+
+        assert_se(p = new(char, NAME_MAX + 1));
+        memset(p, 'x', NAME_MAX);
+        p[NAME_MAX] = '\0';
+
+        assert_se(q = new(char, NAME_MAX + 1));
+        memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 16);
+        q[NAME_MAX - 16] = '\0';
+
+        test_tempfn_random_one(p, NULL, q, 0);
+
+        memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 16);
+        q[NAME_MAX - 16] = '\0';
+
+        test_tempfn_random_one(p, "hoge", q, 0);
 }
 
 static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char *expect, int ret) {
@@ -68,10 +102,13 @@ static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char
 }
 
 TEST(tempfn_xxxxxx) {
+        _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL;
+
         test_tempfn_xxxxxx_one("", NULL, NULL, -EINVAL);
         test_tempfn_xxxxxx_one(".", NULL, NULL, -EADDRNOTAVAIL);
         test_tempfn_xxxxxx_one("..", NULL, NULL, -EINVAL);
         test_tempfn_xxxxxx_one("/", NULL, NULL, -EADDRNOTAVAIL);
+        test_tempfn_xxxxxx_one("foo", "hoge/aaa", NULL, -EINVAL);
 
         test_tempfn_xxxxxx_one("foo", NULL, ".#foo", 0);
         test_tempfn_xxxxxx_one("foo", "bar", ".#barfoo", 0);
@@ -90,6 +127,108 @@ TEST(tempfn_xxxxxx) {
         test_tempfn_xxxxxx_one("./foo/", "bar", ".#barfoo", 0);
         test_tempfn_xxxxxx_one("../foo/", NULL, "../.#foo", 0);
         test_tempfn_xxxxxx_one("../foo/", "bar", "../.#barfoo", 0);
+
+        assert_se(dir = new(char, PATH_MAX - 10));
+        memset(dir, 'x', PATH_MAX - 11);
+        dir[PATH_MAX - 11] = '\0';
+        for (size_t i = 0; i < PATH_MAX - 11; i += NAME_MAX + 1)
+                dir[i] = '/';
+
+        assert_se(p = path_join(dir, "a"));
+        assert_se(q = path_join(dir, ".#a"));
+
+        test_tempfn_xxxxxx_one(p, NULL, q, 0);
+        test_tempfn_xxxxxx_one(p, "b", NULL, -EINVAL);
+
+        p = mfree(p);
+        q = mfree(q);
+
+        assert_se(p = new(char, NAME_MAX + 1));
+        memset(p, 'x', NAME_MAX);
+        p[NAME_MAX] = '\0';
+
+        assert_se(q = new(char, NAME_MAX + 1));
+        memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 6);
+        q[NAME_MAX - 6] = '\0';
+
+        test_tempfn_xxxxxx_one(p, NULL, q, 0);
+
+        memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 6);
+        q[NAME_MAX - 6] = '\0';
+
+        test_tempfn_xxxxxx_one(p, "hoge", q, 0);
+}
+
+static void test_tempfn_random_child_one(const char *p, const char *extra, const char *expect, int ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        r = tempfn_random_child(p, extra, &s);
+        log_info_errno(r, "%s+%s → %s vs. %s (%i/%m vs. %i/%s)", p, strna(extra), strna(s), strna(expect), r, ret, strerror_safe(ret));
+
+        assert_se(!s == !expect);
+        if (s) {
+                const char *suffix;
+
+                assert_se(suffix = startswith(s, expect));
+                assert_se(in_charset(suffix, HEXDIGITS));
+                assert_se(strlen(suffix) == 16);
+        }
+        assert_se(ret == r);
+}
+
+TEST(tempfn_random_child) {
+        _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL;
+
+        test_tempfn_random_child_one("", NULL, ".#", 0);
+        test_tempfn_random_child_one(".", NULL, ".#", 0);
+        test_tempfn_random_child_one("..", NULL, "../.#", 0);
+        test_tempfn_random_child_one("/", NULL, "/.#", 0);
+        test_tempfn_random_child_one("foo", "hoge/aaa", NULL, -EINVAL);
+
+        test_tempfn_random_child_one("foo", NULL, "foo/.#", 0);
+        test_tempfn_random_child_one("foo", "bar", "foo/.#bar", 0);
+        test_tempfn_random_child_one("/tmp/foo", NULL, "/tmp/foo/.#", 0);
+        test_tempfn_random_child_one("/tmp/foo", "bar", "/tmp/foo/.#bar", 0);
+        test_tempfn_random_child_one("./foo", NULL, "foo/.#", 0);
+        test_tempfn_random_child_one("./foo", "bar", "foo/.#bar", 0);
+        test_tempfn_random_child_one("../foo", NULL, "../foo/.#", 0);
+        test_tempfn_random_child_one("../foo", "bar", "../foo/.#bar", 0);
+
+        test_tempfn_random_child_one("foo/", NULL, "foo/.#", 0);
+        test_tempfn_random_child_one("foo/", "bar", "foo/.#bar", 0);
+        test_tempfn_random_child_one("/tmp/foo/", NULL, "/tmp/foo/.#", 0);
+        test_tempfn_random_child_one("/tmp/foo/", "bar", "/tmp/foo/.#bar", 0);
+        test_tempfn_random_child_one("./foo/", NULL, "foo/.#", 0);
+        test_tempfn_random_child_one("./foo/", "bar", "foo/.#bar", 0);
+        test_tempfn_random_child_one("../foo/", NULL, "../foo/.#", 0);
+        test_tempfn_random_child_one("../foo/", "bar", "../foo/.#bar", 0);
+
+        assert_se(dir = new(char, PATH_MAX - 21));
+        memset(dir, 'x', PATH_MAX - 22);
+        dir[PATH_MAX - 22] = '\0';
+        for (size_t i = 0; i < PATH_MAX - 22; i += NAME_MAX + 1)
+                dir[i] = '/';
+
+        assert_se(p = path_join(dir, "a"));
+        assert_se(q = path_join(p, ".#"));
+
+        test_tempfn_random_child_one(p, NULL, q, 0);
+        test_tempfn_random_child_one(p, "b", NULL, -EINVAL);
+
+        p = mfree(p);
+        q = mfree(q);
+
+        assert_se(p = new(char, NAME_MAX + 1));
+        memset(p, 'x', NAME_MAX);
+        p[NAME_MAX] = '\0';
+
+        assert_se(q = path_join(p, ".#"));
+
+        test_tempfn_random_child_one(p, NULL, q, 0);
+
+        assert_se(strextend(&q, "hoge"));
+        test_tempfn_random_child_one(p, "hoge", q, 0);
 }
 
 DEFINE_TEST_MAIN(LOG_DEBUG);