]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fileio: optionally allow interpreting file size as limit
authorLennart Poettering <lennart@poettering.net>
Mon, 21 Jun 2021 12:18:04 +0000 (14:18 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 8 Jul 2021 07:29:53 +0000 (09:29 +0200)
src/basic/fileio.c
src/basic/fileio.h
src/test/test-fileio.c

index 99a44fdea2b5453a951e7f8dbe5c5f2327e7d468..19fe6e214c978d8b6bedc0e8fbcc99abaed4a591 100644 (file)
@@ -522,18 +522,17 @@ int read_full_stream_full(
                 size_t *ret_size) {
 
         _cleanup_free_ char *buf = NULL;
-        size_t n, n_next, l;
+        size_t n, n_next = 0, l;
         int fd, r;
 
         assert(f);
         assert(ret_contents);
         assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
+        assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER));
 
-        if (offset != UINT64_MAX && offset > LONG_MAX)
+        if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */
                 return -ERANGE;
 
-        n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */
-
         fd = fileno(f);
         if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see
                         * fmemopen()), let's optimize our buffering */
@@ -543,20 +542,20 @@ int read_full_stream_full(
                         return -errno;
 
                 if (S_ISREG(st.st_mode)) {
-                        if (size == SIZE_MAX) {
+
+                        /* Try to start with the right file size if we shall read the file in full. Note
+                         * that we increase the size to read here by one, so that the first read attempt
+                         * already makes us notice the EOF. If the reported size of the file is zero, we
+                         * avoid this logic however, since quite likely it might be a virtual file in procfs
+                         * that all report a zero file size. */
+
+                        if (st.st_size > 0 &&
+                            (size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) {
+
                                 uint64_t rsize =
                                         LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset);
 
-                                /* Safety check */
-                                if (rsize > READ_FULL_BYTES_MAX)
-                                        return -E2BIG;
-
-                                /* Start with the right file size. Note that we increase the size to read
-                                 * here by one, so that the first read attempt already makes us notice the
-                                 * EOF. If the reported size of the file is zero, we avoid this logic
-                                 * however, since quite likely it might be a virtual file in procfs that all
-                                 * report a zero file size. */
-                                if (st.st_size > 0)
+                                if (rsize < SIZE_MAX) /* overflow check */
                                         n_next = rsize + 1;
                         }
 
@@ -565,6 +564,17 @@ int read_full_stream_full(
                 }
         }
 
+        /* If we don't know how much to read, figure it out now. If we shall read a part of the file, then
+         * allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if
+         * READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start
+         * with LINE_MAX, under assumption the file is most likely much shorter. */
+        if (n_next == 0)
+                n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX;
+
+        /* Never read more than we need to determine that our own limit is hit */
+        if (n_next > READ_FULL_BYTES_MAX)
+                n_next = READ_FULL_BYTES_MAX + 1;
+
         if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0)
                 return -errno;
 
@@ -573,6 +583,11 @@ int read_full_stream_full(
                 char *t;
                 size_t k;
 
+                /* If we shall fail when reading overly large data, then read exactly one byte more than the
+                 * specified size at max, since that'll tell us if there's anymore data beyond the limit*/
+                if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size)
+                        n_next = size + 1;
+
                 if (flags & READ_FULL_FILE_SECURE) {
                         t = malloc(n_next + 1);
                         if (!t) {
@@ -606,14 +621,18 @@ int read_full_stream_full(
                 if (feof(f))
                         break;
 
-                if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
+                if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
                         assert(l == size);
                         break;
                 }
 
                 assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
 
-                /* Safety check */
+                if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) {
+                        r = -E2BIG;
+                        goto finalize;
+                }
+
                 if (n >= READ_FULL_BYTES_MAX) {
                         r = -E2BIG;
                         goto finalize;
index c28b17fef52691c79fe54f10f545ee3225882694..af797cfafdbf5372a1a8f946c8efdf09d2fb0f77 100644 (file)
@@ -39,6 +39,7 @@ typedef enum {
         READ_FULL_FILE_UNHEX               = 1 << 2, /* hex decode what we read */
         READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */
         READ_FULL_FILE_CONNECT_SOCKET      = 1 << 4, /* if socket inode, connect to it and read off it */
+        READ_FULL_FILE_FAIL_WHEN_LARGER    = 1 << 5, /* fail loading if file is larger than specified size */
 } ReadFullFileFlags;
 
 int fopen_unlocked(const char *path, const char *options, FILE **ret);
index 321b544448d7812e92e863d88a05db7fecc8b086..2b6ef495aaf7a562e99cebda17fb6a2d15fe13d9 100644 (file)
@@ -999,6 +999,25 @@ static void test_read_full_file_offset_size(void) {
         assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
         rbuf = mfree(rbuf);
 
+        assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
+        assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
+        assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf), READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
+        assert_se(rbuf_size == sizeof(buf));
+        assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
+        rbuf = mfree(rbuf);
+
+        assert_se(read_full_file_full(AT_FDCWD, fn, 47, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
+        assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG);
+        assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
+        assert_se(rbuf_size == sizeof(buf)-47);
+        assert_se(memcmp(buf+47, rbuf, rbuf_size) == 0);
+        rbuf = mfree(rbuf);
+
+        assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)+1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0);
+        assert_se(rbuf_size == sizeof(buf));
+        assert_se(memcmp(buf, rbuf, rbuf_size) == 0);
+        rbuf = mfree(rbuf);
+
         assert_se(read_full_file_full(AT_FDCWD, fn, 1234, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0);
         assert_se(rbuf_size == sizeof(buf) - 1234);
         assert_se(memcmp(buf + 1234, rbuf, rbuf_size) == 0);