From ccaed96bb280302314076d55ea6be2411e5ad026 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 3 Feb 2026 12:43:35 -0800 Subject: [PATCH] lib: use tmpfile when writing out bpfilter state The bpfilter daemon updates persistent state by reopening the existing file, truncating it, then overwriting the state. If the daemon crashes during this process, when it restarts it will find either no data, or malformed data. Turn the state update atomic by instead writing the new persistent data to a temporary file, then moving it to the correct location. --- src/libbpfilter/helper.c | 43 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/libbpfilter/helper.c b/src/libbpfilter/helper.c index a1036d29..45b37982 100644 --- a/src/libbpfilter/helper.c +++ b/src/libbpfilter/helper.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -97,26 +99,59 @@ int bf_read_file(const char *path, void **buf, size_t *len) return 0; } +static int _bf_get_tmpfile_fd(const char *base, char tmpfile_path[static PATH_MAX]) +{ + _cleanup_free_ char *tmp = NULL; + size_t path_len; + char *dir; + int fd; + + /* The dirname call may modify its argument. */ + tmp = strdup(base); + if (!tmp) + return bf_err_r(ENOMEM, "could not duplicate path %s", base); + + dir = dirname(tmp); + + path_len = snprintf(tmpfile_path, PATH_MAX, "%s%s", dir, "/bpfilter.tmp.XXXXXX"); + if (path_len >= PATH_MAX) + return bf_err_r(ENAMETOOLONG, "tmpfile name too long (%lu bytes)", path_len); + + fd = mkstemp(tmpfile_path); + if (fd < 0) + return bf_err_r(errno, "failed to open %s", tmpfile_path); + + return fd; +} + int bf_write_file(const char *path, const void *buf, size_t len) { _cleanup_close_ int fd = -1; ssize_t r; + int err; + char tmpfile_path[PATH_MAX]; bf_assert(path); bf_assert(buf); - fd = open(path, O_TRUNC | O_CREAT | O_WRONLY, OPEN_MODE_644); + fd = _bf_get_tmpfile_fd(path, tmpfile_path); if (fd < 0) - return bf_err_r(errno, "failed to open %s", path); + return bf_err_r(fd, "failed to get temporary file name"); r = write(fd, buf, len); if (r < 0) - return bf_err_r(errno, "failed to write to %s", path); + return bf_err_r(errno, "failed to write to %s", tmpfile_path); if ((size_t)r != len) - return bf_err_r(EIO, "can't write full data to %s", path); + return bf_err_r(EIO, "can't write full data to %s", tmpfile_path); closep(&fd); + if (rename(tmpfile_path, path) < 0) { + err = errno; + unlink(tmpfile_path); + return bf_err_r(err, "failed to move %s to %s", tmpfile_path, path); + } + return 0; }