From: Lennart Poettering Date: Mon, 21 Jun 2021 12:18:04 +0000 (+0200) Subject: fileio: optionally allow interpreting file size as limit X-Git-Tag: v250-rc1~980^2~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7b0da71d4976066f53dffa250d659d725f2d486c;p=thirdparty%2Fsystemd.git fileio: optionally allow interpreting file size as limit --- diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 99a44fdea2b..19fe6e214c9 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -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; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index c28b17fef52..af797cfafdb 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -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); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 321b544448d..2b6ef495aaf 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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);