clang-tidy, starting from LLVM version 16, up to at least LLVM version 19, now checks that we detect and handle errors for snprintf() as requested by CERT C rule ERR33-C. These warnings were logged with LLVM version 19.1.2 (at least Debian and Fedora match): /home/sbrivio/passt/arch.c:43:3: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 43 | snprintf(new_path, PATH_MAX + sizeof(".avx2"), "%s.avx2", exe); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/arch.c:43:3: note: cast the expression to void to silence this warning /home/sbrivio/passt/conf.c:577:4: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 577 | snprintf(netns, PATH_MAX, "/proc/%ld/ns/net", pidval); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/conf.c:577:4: note: cast the expression to void to silence this warning /home/sbrivio/passt/conf.c:579:5: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 579 | snprintf(userns, PATH_MAX, "/proc/%ld/ns/user", | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 580 | pidval); | ~~~~~~~ /home/sbrivio/passt/conf.c:579:5: note: cast the expression to void to silence this warning /home/sbrivio/passt/pasta.c:105:2: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 105 | snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/pasta.c:105:2: note: cast the expression to void to silence this warning /home/sbrivio/passt/pasta.c:242:2: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 242 | snprintf(uidmap, BUFSIZ, "0 %u 1", uid); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/pasta.c:242:2: note: cast the expression to void to silence this warning /home/sbrivio/passt/pasta.c:243:2: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 243 | snprintf(gidmap, BUFSIZ, "0 %u 1", gid); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/pasta.c:243:2: note: cast the expression to void to silence this warning /home/sbrivio/passt/tap.c:1155:4: error: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c,-warnings-as-errors] 1155 | snprintf(path, UNIX_PATH_MAX - 1, UNIX_SOCK_PATH, i); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/sbrivio/passt/tap.c:1155:4: note: cast the expression to void to silence this warning Don't silence the warnings as they might actually have some merit. Add an snprintf_check() function, instead, checking that we're not truncating messages while printing to buffers and terminating if we do. Signed-off-by: Stefano Brivio <sbrivio(a)redhat.com> --- arch.c | 4 +++- conf.c | 11 +++++++---- pasta.c | 7 ++++--- tap.c | 8 +++++--- util.c | 22 ++++++++++++++++++++++ util.h | 3 +++ 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/arch.c b/arch.c index 04bebfc..edbe666 100644 --- a/arch.c +++ b/arch.c @@ -19,6 +19,7 @@ #include <unistd.h> #include "log.h" +#include "util.h" /** * arch_avx2_exec() - Switch to AVX2 build if supported @@ -40,7 +41,8 @@ void arch_avx2_exec(char **argv) if (__builtin_cpu_supports("avx2")) { char new_path[PATH_MAX + sizeof(".avx2")]; - snprintf(new_path, PATH_MAX + sizeof(".avx2"), "%s.avx2", exe); + snprintf_check("Can't build AVX2 executable path", new_path, + PATH_MAX + sizeof(".avx2"), "%s.avx2", exe); execve(new_path, argv, environ); warn_perror("Can't run AVX2 build, using non-AVX2 version"); } diff --git a/conf.c b/conf.c index b3b5342..2122600 100644 --- a/conf.c +++ b/conf.c @@ -574,10 +574,13 @@ static void conf_pasta_ns(int *netns_only, char *userns, char *netns, if (pidval < 0 || pidval > INT_MAX) die("Invalid PID %s", argv[optind]); - snprintf(netns, PATH_MAX, "/proc/%ld/ns/net", pidval); - if (!*userns) - snprintf(userns, PATH_MAX, "/proc/%ld/ns/user", - pidval); + snprintf_check("Can't build netns path", netns, + PATH_MAX, "/proc/%ld/ns/net", pidval); + if (!*userns) { + snprintf_check("Can't build userns path", + userns, PATH_MAX, + "/proc/%ld/ns/user", pidval); + } } } diff --git a/pasta.c b/pasta.c index 307fb4a..ce9ae7a 100644 --- a/pasta.c +++ b/pasta.c @@ -102,7 +102,8 @@ static int pasta_wait_for_ns(void *arg) int flags = O_RDONLY | O_CLOEXEC; char ns[PATH_MAX]; - snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid); + snprintf_check("Can't build netns path", ns, + PATH_MAX, "/proc/%i/ns/net", pasta_child_pid); do { while ((c->pasta_netns_fd = open(ns, flags)) < 0) { if (errno != ENOENT) @@ -239,8 +240,8 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, c->quiet = 1; /* Configure user and group mappings */ - snprintf(uidmap, BUFSIZ, "0 %u 1", uid); - snprintf(gidmap, BUFSIZ, "0 %u 1", gid); + snprintf_check("Can't build uidmap", uidmap, BUFSIZ, "0 %u 1", uid); + snprintf_check("Can't build gidmap", gidmap, BUFSIZ, "0 %u 1", gid); if (write_file("/proc/self/uid_map", uidmap) || write_file("/proc/self/setgroups", "deny") || diff --git a/tap.c b/tap.c index c53a39b..51eb134 100644 --- a/tap.c +++ b/tap.c @@ -1149,10 +1149,12 @@ int tap_sock_unix_open(char *sock_path) char *path = addr.sun_path; int ex, ret; - if (*sock_path) + if (*sock_path) { memcpy(path, sock_path, UNIX_PATH_MAX); - else - snprintf(path, UNIX_PATH_MAX - 1, UNIX_SOCK_PATH, i); + } else { + snprintf_check("Can't build UNIX domain path", path, + UNIX_PATH_MAX - 1, UNIX_SOCK_PATH, i); + } ex = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); if (ex < 0) diff --git a/util.c b/util.c index eba7d52..eff6787 100644 --- a/util.c +++ b/util.c @@ -749,3 +749,25 @@ void close_open_files(int argc, char **argv) if (rc) die_perror("Failed to close files leaked by parent"); } + +/** + * snprintf_check() - snprintf() wrapper, terminating on truncation + * @errmsg: Error string to be printed while terminating + * @str: Output buffer + * @size: Maximum size to write to @str + * @format: Message + */ +void snprintf_check(const char *errstr, + char *str, size_t size, const char *format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + + rc = snprintf(str, size, format, ap); + if (rc < 0 || (size_t)rc >= size) + die("%s", errstr); + + va_end(ap); +} diff --git a/util.h b/util.h index 2c1e08e..8449d00 100644 --- a/util.h +++ b/util.h @@ -11,6 +11,7 @@ #include <stdbool.h> #include <stddef.h> #include <stdint.h> +#include <stdio.h> #include <string.h> #include <signal.h> #include <arpa/inet.h> @@ -200,6 +201,8 @@ int write_file(const char *path, const char *buf); int write_all_buf(int fd, const void *buf, size_t len); int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip); void close_open_files(int argc, char **argv); +void snprintf_check(const char *errstr, + char *str, size_t size, const char *format, ...); /** * af_name() - Return name of an address family -- 2.43.0