diff options
author | 2018-12-18 20:49:19 +0100 | |
---|---|---|
committer | 2018-12-18 20:49:19 +0100 | |
commit | be2e1823efe7d7ae8bf88c133f075edcdf726aff (patch) | |
tree | fa6cd7bb88b4bf16e3e4c66118d495a2a76bba12 /src | |
parent | Merge pull request #11203 from keszybz/json-no-slash-escaping (diff) | |
parent | test-fileio: test safe_fgetc directly (diff) | |
download | systemd-be2e1823efe7d7ae8bf88c133f075edcdf726aff.tar.gz systemd-be2e1823efe7d7ae8bf88c133f075edcdf726aff.tar.bz2 systemd-be2e1823efe7d7ae8bf88c133f075edcdf726aff.zip |
Merge pull request #11182 from poettering/fileio-more-paranoia
More safety checks for fileio.c
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/fileio.c | 108 | ||||
-rw-r--r-- | src/basic/fileio.h | 16 | ||||
-rw-r--r-- | src/basic/process-util.c | 62 | ||||
-rw-r--r-- | src/basic/terminal-util.c | 13 | ||||
-rw-r--r-- | src/machine/machine.c | 12 | ||||
-rw-r--r-- | src/machine/machined-dbus.c | 8 | ||||
-rw-r--r-- | src/test/test-fileio.c | 67 |
7 files changed, 176 insertions, 110 deletions
diff --git a/src/basic/fileio.c b/src/basic/fileio.c index b22fcd030..66e127345 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -667,46 +667,6 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) return fputs(s, f); } -int read_nul_string(FILE *f, char **ret) { - _cleanup_free_ char *x = NULL; - size_t allocated = 0, n = 0; - - assert(f); - assert(ret); - - /* Reads a NUL-terminated string from the specified file. */ - - for (;;) { - int c; - - if (!GREEDY_REALLOC(x, allocated, n+2)) - return -ENOMEM; - - c = fgetc(f); - if (c == 0) /* Terminate at NUL byte */ - break; - if (c == EOF) { - if (ferror(f)) - return -errno; - break; /* Terminate at EOF */ - } - - x[n++] = (char) c; - } - - if (x) - x[n] = 0; - else { - x = new0(char, 1); - if (!x) - return -ENOMEM; - } - - *ret = TAKE_PTR(x); - - return 0; -} - /* A bitmask of the EOL markers we know */ typedef enum EndOfLineMarker { EOL_NONE = 0, @@ -715,11 +675,15 @@ typedef enum EndOfLineMarker { EOL_THIRTEEN = 1 << 2, /* \r (aka CR) */ } EndOfLineMarker; -static EndOfLineMarker categorize_eol(char c) { - if (c == '\n') - return EOL_TEN; - if (c == '\r') - return EOL_THIRTEEN; +static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) { + + if (!IN_SET(flags, READ_LINE_ONLY_NUL)) { + if (c == '\n') + return EOL_TEN; + if (c == '\r') + return EOL_THIRTEEN; + } + if (c == '\0') return EOL_ZERO; @@ -728,9 +692,10 @@ static EndOfLineMarker categorize_eol(char c) { DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile); -int read_line(FILE *f, size_t limit, char **ret) { +int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { size_t n = 0, allocated = 0, count = 0; _cleanup_free_ char *buffer = NULL; + int r; assert(f); @@ -770,7 +735,7 @@ int read_line(FILE *f, size_t limit, char **ret) { for (;;) { EndOfLineMarker eol; - int c; + char c; if (n >= limit) return -ENOBUFS; @@ -778,20 +743,13 @@ int read_line(FILE *f, size_t limit, char **ret) { if (count >= INT_MAX) /* We couldn't return the counter anymore as "int", hence refuse this */ return -ENOBUFS; - errno = 0; - c = fgetc_unlocked(f); - if (c == EOF) { - /* if we read an error, and have no data to return, then propagate the error */ - if (ferror_unlocked(f) && n == 0) - return errno > 0 ? -errno : -EIO; - - /* EOF is line ending too. */ + r = safe_fgetc(f, &c); + if (r < 0) + return r; + if (r == 0) /* EOF is definitely EOL */ break; - } - - count++; - eol = categorize_eol(c); + eol = categorize_eol(c, flags); if (FLAGS_SET(previous_eol, EOL_ZERO) || (eol == EOL_NONE && previous_eol != EOL_NONE) || @@ -800,10 +758,11 @@ int read_line(FILE *f, size_t limit, char **ret) { * EOL marker has been seen right before? In either of these three cases we are * done. But first, let's put this character back in the queue. */ assert_se(ungetc(c, f) != EOF); - count--; break; } + count++; + if (eol != EOL_NONE) { previous_eol |= eol; continue; @@ -813,7 +772,7 @@ int read_line(FILE *f, size_t limit, char **ret) { if (!GREEDY_REALLOC(buffer, allocated, n + 2)) return -ENOMEM; - buffer[n] = (char) c; + buffer[n] = c; } n++; @@ -828,3 +787,30 @@ int read_line(FILE *f, size_t limit, char **ret) { return (int) count; } + +int safe_fgetc(FILE *f, char *ret) { + int k; + + assert(f); + + /* A safer version of plain fgetc(): let's propagate the error that happened while reading as such, and + * separate the EOF condition from the byte read, to avoid those confusion signed/unsigned issues fgetc() + * has. */ + + errno = 0; + k = fgetc(f); + if (k == EOF) { + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + if (ret) + *ret = 0; + + return 0; + } + + if (ret) + *ret = k; + + return 1; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 848024607..53e3f4ef5 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -61,6 +61,18 @@ int read_timestamp_file(const char *fn, usec_t *ret); int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); -int read_nul_string(FILE *f, char **ret); +typedef enum ReadLineFlags { + READ_LINE_ONLY_NUL = 1 << 0, +} ReadLineFlags; -int read_line(FILE *f, size_t limit, char **ret); +int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret); + +static inline int read_line(FILE *f, size_t limit, char **ret) { + return read_line_full(f, limit, 0, ret); +} + +static inline int read_nul_string(FILE *f, size_t limit, char **ret) { + return read_line_full(f, limit, READ_LINE_ONLY_NUL, ret); +} + +int safe_fgetc(FILE *f, char *ret); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 5b700df33..3cfaceea8 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -600,12 +600,14 @@ int get_process_root(pid_t pid, char **root) { return get_process_link_contents(p, root); } +#define ENVIRONMENT_BLOCK_MAX (5U*1024U*1024U) + int get_process_environ(pid_t pid, char **env) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *outcome = NULL; - int c; - const char *p; size_t allocated = 0, sz = 0; + const char *p; + int r; assert(pid >= 0); assert(env); @@ -621,23 +623,28 @@ int get_process_environ(pid_t pid, char **env) { (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - while ((c = fgetc(f)) != EOF) { + for (;;) { + char c; + + if (sz >= ENVIRONMENT_BLOCK_MAX) + return -ENOBUFS; + if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) return -ENOMEM; + r = safe_fgetc(f, &c); + if (r < 0) + return r; + if (r == 0) + break; + if (c == '\0') outcome[sz++] = '\n'; else sz += cescape_char(c, outcome + sz); } - if (!outcome) { - outcome = strdup(""); - if (!outcome) - return -ENOMEM; - } else - outcome[sz] = '\0'; - + outcome[sz] = '\0'; *env = TAKE_PTR(outcome); return 0; @@ -870,9 +877,9 @@ int kill_and_sigcont(pid_t pid, int sig) { int getenv_for_pid(pid_t pid, const char *field, char **ret) { _cleanup_fclose_ FILE *f = NULL; char *value = NULL; - bool done = false; const char *path; - size_t l; + size_t l, sum = 0; + int r; assert(pid >= 0); assert(field); @@ -895,6 +902,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { return 1; } + if (!pid_is_valid(pid)) + return -EINVAL; + path = procfs_file_alloca(pid, "environ"); f = fopen(path, "re"); @@ -908,24 +918,19 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { (void) __fsetlocking(f, FSETLOCKING_BYCALLER); l = strlen(field); + for (;;) { + _cleanup_free_ char *line = NULL; - do { - char line[LINE_MAX]; - size_t i; - - for (i = 0; i < sizeof(line)-1; i++) { - int c; + if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */ + return -ENOBUFS; - c = getc(f); - if (_unlikely_(c == EOF)) { - done = true; - break; - } else if (c == 0) - break; + r = read_nul_string(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) /* EOF */ + break; - line[i] = c; - } - line[i] = 0; + sum += r; if (strneq(line, field, l) && line[l] == '=') { value = strdup(line + l + 1); @@ -935,8 +940,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { *ret = value; return 1; } - - } while (!done); + } *ret = NULL; return 0; diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ceba7d0ff..0f3812072 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -96,7 +96,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { new_termios.c_cc[VTIME] = 0; if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { - int c; + char c; if (t != USEC_INFINITY) { if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { @@ -105,17 +105,12 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { } } - errno = 0; - c = fgetc(f); - if (c == EOF) - r = ferror(f) && errno > 0 ? -errno : -EIO; - else - r = 0; - + r = safe_fgetc(f, &c); (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios); - if (r < 0) return r; + if (r == 0) + return -EIO; if (need_nl) *need_nl = c != '\n'; diff --git a/src/machine/machine.c b/src/machine/machine.c index beb5b3566..4f89ac026 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -594,7 +594,7 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { uid_t uid_base, uid_shift, uid_range; gid_t gid_base, gid_shift, gid_range; _cleanup_fclose_ FILE *f = NULL; - int k; + int k, r; assert(m); assert(ret); @@ -643,7 +643,10 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { return -ENXIO; /* If there's more than one line, then we don't support this mapping. */ - if (fgetc(f) != EOF) + r = safe_fgetc(f, NULL); + if (r < 0) + return r; + if (r != 0) /* Insist on EOF */ return -ENXIO; fclose(f); @@ -664,7 +667,10 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { } /* If there's more than one line, then we don't support this file. */ - if (fgetc(f) != EOF) + r = safe_fgetc(f, NULL); + if (r < 0) + return r; + if (r != 0) /* Insist on EOF */ return -ENXIO; /* If the UID and GID mapping doesn't match, we don't support this mapping. */ diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 9afae72ae..fea9cc263 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -637,8 +637,8 @@ static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) { if (success) /* The resulting temporary file could not be updated, ignore it. */ return ret; - r = read_nul_string(f, &name); - if (r < 0 || isempty(name)) /* Same here... */ + r = read_nul_string(f, LONG_LINE_MAX, &name); + if (r <= 0) /* Same here... */ return ret; return sd_bus_error_set_errnof(error, ret, "Failed to remove image %s: %m", name); @@ -660,10 +660,10 @@ static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) { _cleanup_free_ char *name = NULL; uint64_t size; - r = read_nul_string(f, &name); + r = read_nul_string(f, LONG_LINE_MAX, &name); if (r < 0) return r; - if (isempty(name)) /* reached the end */ + if (r == 0) /* reached the end */ break; errno = 0; diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index fd0cc27de..f75b75757 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -610,9 +610,36 @@ static void test_tempfn(void) { free(ret); } +static const char chars[] = + "Aąę„”\n루"; + +static void test_fgetc(void) { + _cleanup_fclose_ FILE *f = NULL; + char c; + + f = fmemopen((void*) chars, sizeof(chars), "re"); + assert_se(f); + + for (unsigned i = 0; i < sizeof(chars); i++) { + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + + assert_se(ungetc(c, f) != EOF); + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + + /* Check that ungetc doesn't care about unsigned char vs signed char */ + assert_se(ungetc((unsigned char) c, f) != EOF); + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + } + + assert_se(safe_fgetc(f, &c) == 0); +} + static const char buffer[] = "Some test data\n" - "Some weird line\r" + "루Non-ascii chars: ąę„”\n" "terminators\r\n" "and even more\n\r" "now the same with a NUL\n\0" @@ -631,7 +658,7 @@ static void test_read_line_one_file(FILE *f) { assert_se(read_line(f, (size_t) -1, &line) == 15 && streq(line, "Some test data")); line = mfree(line); - assert_se(read_line(f, (size_t) -1, &line) == 16 && streq(line, "Some weird line")); + assert_se(read_line(f, (size_t) -1, &line) > 0 && streq(line, "루Non-ascii chars: ąę„”")); line = mfree(line); assert_se(read_line(f, (size_t) -1, &line) == 13 && streq(line, "terminators")); @@ -748,6 +775,40 @@ static void test_read_line4(void) { } } +static void test_read_nul_string(void) { + static const char test[] = "string nr. 1\0" + "string nr. 2\n\0" + "empty string follows\0" + "\0" + "final string\n is empty\0" + "\0"; + + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *s = NULL; + + assert_se(f = fmemopen((void*) test, sizeof(test)-1, "r")); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 13 && streq_ptr(s, "string nr. 1")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 14 && streq_ptr(s, "string nr. 2\n")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 21 && streq_ptr(s, "empty string follows")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 1 && streq_ptr(s, "")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 23 && streq_ptr(s, "final string\n is empty")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 1 && streq_ptr(s, "")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 0 && streq_ptr(s, "")); +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -767,10 +828,12 @@ int main(int argc, char *argv[]) { test_search_and_fopen_nulstr(); test_writing_tmpfile(); test_tempfn(); + test_fgetc(); test_read_line(); test_read_line2(); test_read_line3(); test_read_line4(); + test_read_nul_string(); return 0; } |