b11b5f
From 7a3843972ea290daf1bec5e1133db654749b8c02 Mon Sep 17 00:00:00 2001
b11b5f
From: Franck Bui <fbui@suse.com>
b11b5f
Date: Tue, 22 Oct 2019 16:09:21 +0200
b11b5f
Subject: [PATCH] fileio: introduce read_full_virtual_file() for reading
b11b5f
 virtual files in sysfs, procfs
b11b5f
b11b5f
Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work
b11b5f
with two sorts of virtual files.
b11b5f
b11b5f
One sort uses "seq_file", and the results of the first read are buffered for
b11b5f
the second read. The other sort uses "raw" reads which always go direct to the
b11b5f
device.
b11b5f
b11b5f
In the later case, the content of the virtual file must be retrieved with a
b11b5f
single read otherwise subsequent read might get the new value instead of
b11b5f
finding EOF immediately. That's the reason why the usage of fread(3) is
b11b5f
prohibited in this case as it always performs a second call to read(2) looking
b11b5f
for EOF which is subject to the race described previously.
b11b5f
b11b5f
Fixes: #13585.
b11b5f
(cherry picked from commit 21b40f16622f171a9969dc334d74fb5eb2f575c2)
b11b5f
b11b5f
Related: #2117948
b11b5f
---
b11b5f
 src/basic/fileio.c                   | 115 ++++++++++++++++++++++++++-
b11b5f
 src/basic/fileio.h                   |   1 +
b11b5f
 src/libsystemd/sd-device/sd-device.c |   2 +-
b11b5f
 3 files changed, 113 insertions(+), 5 deletions(-)
b11b5f
b11b5f
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
b11b5f
index 6b0bad5b71..733fb42463 100644
b11b5f
--- a/src/basic/fileio.c
b11b5f
+++ b/src/basic/fileio.c
b11b5f
@@ -276,6 +276,113 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
b11b5f
         return 1;
b11b5f
 }
b11b5f
 
b11b5f
+int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) {
b11b5f
+        _cleanup_free_ char *buf = NULL;
b11b5f
+        _cleanup_close_ int fd = -1;
b11b5f
+        struct stat st;
b11b5f
+        size_t n, size;
b11b5f
+        int n_retries;
b11b5f
+        char *p;
b11b5f
+
b11b5f
+        assert(ret_contents);
b11b5f
+
b11b5f
+        /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work
b11b5f
+         * with two sorts of virtual files. One sort uses "seq_file", and the results of
b11b5f
+         * the first read are buffered for the second read. The other sort uses "raw"
b11b5f
+         * reads which always go direct to the device. In the latter case, the content of
b11b5f
+         * the virtual file must be retrieved with a single read otherwise a second read
b11b5f
+         * might get the new value instead of finding EOF immediately. That's the reason
b11b5f
+         * why the usage of fread(3) is prohibited in this case as it always performs a
b11b5f
+         * second call to read(2) looking for EOF. See issue 13585. */
b11b5f
+
b11b5f
+        fd = open(filename, O_RDONLY|O_CLOEXEC);
b11b5f
+        if (fd < 0)
b11b5f
+                return -errno;
b11b5f
+
b11b5f
+        /* Start size for files in /proc which usually report a file size of 0. */
b11b5f
+        size = LINE_MAX / 2;
b11b5f
+
b11b5f
+        /* Limit the number of attempts to read the number of bytes returned by fstat(). */
b11b5f
+        n_retries = 3;
b11b5f
+
b11b5f
+        for (;;) {
b11b5f
+                if (n_retries <= 0)
b11b5f
+                        return -EIO;
b11b5f
+
b11b5f
+                if (fstat(fd, &st) < 0)
b11b5f
+                        return -errno;
b11b5f
+
b11b5f
+                if (!S_ISREG(st.st_mode))
b11b5f
+                        return -EBADF;
b11b5f
+
b11b5f
+                /* Be prepared for files from /proc which generally report a file size of 0. */
b11b5f
+                if (st.st_size > 0) {
b11b5f
+                        size = st.st_size;
b11b5f
+                        n_retries--;
b11b5f
+                } else
b11b5f
+                        size = size * 2;
b11b5f
+
b11b5f
+                if (size > READ_FULL_BYTES_MAX)
b11b5f
+                        return -E2BIG;
b11b5f
+
b11b5f
+                p = realloc(buf, size + 1);
b11b5f
+                if (!p)
b11b5f
+                        return -ENOMEM;
b11b5f
+                buf = TAKE_PTR(p);
b11b5f
+
b11b5f
+                for (;;) {
b11b5f
+                        ssize_t k;
b11b5f
+
b11b5f
+                        /* Read one more byte so we can detect whether the content of the
b11b5f
+                         * file has already changed or the guessed size for files from /proc
b11b5f
+                         * wasn't large enough . */
b11b5f
+                        k = read(fd, buf, size + 1);
b11b5f
+                        if (k >= 0) {
b11b5f
+                                n = k;
b11b5f
+                                break;
b11b5f
+                        }
b11b5f
+
b11b5f
+                        if (errno != -EINTR)
b11b5f
+                                return -errno;
b11b5f
+                }
b11b5f
+
b11b5f
+                /* Consider a short read as EOF */
b11b5f
+                if (n <= size)
b11b5f
+                        break;
b11b5f
+
b11b5f
+                /* Hmm... either we read too few bytes from /proc or less likely the content
b11b5f
+                 * of the file might have been changed (and is now bigger) while we were
b11b5f
+                 * processing, let's try again either with a bigger guessed size or the new
b11b5f
+                 * file size. */
b11b5f
+
b11b5f
+                if (lseek(fd, 0, SEEK_SET) < 0)
b11b5f
+                        return -errno;
b11b5f
+        }
b11b5f
+
b11b5f
+        if (n < size) {
b11b5f
+                p = realloc(buf, n + 1);
b11b5f
+                if (!p)
b11b5f
+                        return -ENOMEM;
b11b5f
+                buf = TAKE_PTR(p);
b11b5f
+        }
b11b5f
+
b11b5f
+        if (!ret_size) {
b11b5f
+                /* Safety check: if the caller doesn't want to know the size of what we
b11b5f
+                 * just read it will rely on the trailing NUL byte. But if there's an
b11b5f
+                 * embedded NUL byte, then we should refuse operation as otherwise
b11b5f
+                 * there'd be ambiguity about what we just read. */
b11b5f
+
b11b5f
+                if (memchr(buf, 0, n))
b11b5f
+                        return -EBADMSG;
b11b5f
+        } else
b11b5f
+                *ret_size = n;
b11b5f
+
b11b5f
+        buf[n] = 0;
b11b5f
+        *ret_contents = TAKE_PTR(buf);
b11b5f
+
b11b5f
+        return 0;
b11b5f
+}
b11b5f
+
b11b5f
 int read_full_stream(FILE *f, char **contents, size_t *size) {
b11b5f
         _cleanup_free_ char *buf = NULL;
b11b5f
         struct stat st;
b11b5f
@@ -300,9 +407,9 @@ int read_full_stream(FILE *f, char **contents, size_t *size) {
b11b5f
                         if (st.st_size > READ_FULL_BYTES_MAX)
b11b5f
                                 return -E2BIG;
b11b5f
 
b11b5f
-                        /* Start with the right file size, but be prepared for files from /proc which generally report a file
b11b5f
-                         * size of 0. Note that we increase the size to read here by one, so that the first read attempt
b11b5f
-                         * already makes us notice the EOF. */
b11b5f
+                        /* Start with the right file size. Note that we increase the size
b11b5f
+                         * to read here by one, so that the first read attempt already
b11b5f
+                         * makes us notice the EOF. */
b11b5f
                         if (st.st_size > 0)
b11b5f
                                 n = st.st_size + 1;
b11b5f
                 }
b11b5f
@@ -986,7 +1093,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
b11b5f
         assert(pattern);
b11b5f
         assert(field);
b11b5f
 
b11b5f
-        r = read_full_file(filename, &status, NULL);
b11b5f
+        r = read_full_virtual_file(filename, &status, NULL);
b11b5f
         if (r < 0)
b11b5f
                 return r;
b11b5f
 
b11b5f
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
b11b5f
index 77e6206e95..c6ad375b8d 100644
b11b5f
--- a/src/basic/fileio.h
b11b5f
+++ b/src/basic/fileio.h
b11b5f
@@ -38,6 +38,7 @@ int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *f
b11b5f
 int read_one_line_file(const char *fn, char **line);
b11b5f
 int read_full_file(const char *fn, char **contents, size_t *size);
b11b5f
 int read_full_stream(FILE *f, char **contents, size_t *size);
b11b5f
+int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size);
b11b5f
 
b11b5f
 int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
b11b5f
 
b11b5f
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
b11b5f
index be29053f8c..49750ba9d7 100644
b11b5f
--- a/src/libsystemd/sd-device/sd-device.c
b11b5f
+++ b/src/libsystemd/sd-device/sd-device.c
b11b5f
@@ -1798,7 +1798,7 @@ _public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr,
b11b5f
                 size_t size;
b11b5f
 
b11b5f
                 /* read attribute value */
b11b5f
-                r = read_full_file(path, &value, &size);
b11b5f
+                r = read_full_virtual_file(path, &value, &size);
b11b5f
                 if (r < 0)
b11b5f
                         return r;
b11b5f