From 10895792d2afd1a25eaafe3826482ec745f1e092 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Sun, 3 May 2026 19:37:27 +0100 Subject: [PATCH] When opening /dev/null internally, check that it really is a null device, and refuse to use it if not (#192). --- src/include/pv.h | 7 +++++ src/main/options.c | 3 +++ src/pv/file.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/src/include/pv.h b/src/include/pv.h index cca38c1..c7a21c1 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -205,6 +205,13 @@ extern void pv_error(char *, ...); */ extern void pv_perror(char *, ...); +/* + * Check that a file descriptor is open on /dev/null, returning true if so. + * Otherwise return false; if the second argument is true, reports the + * problem with pv_error() before returning. + */ +extern bool pv_fd_is_dev_null(int, bool); + /* * Create a new state structure, and return it, or 0 (NULL) on error. diff --git a/src/main/options.c b/src/main/options.c index 1e48cf2..3f69e95 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -216,6 +216,9 @@ static bool opts_watchfd_processname(opts_t opts, const char *process_name) pv_perror("%s", "/dev/null"); exit(EXIT_FAILURE); } + if (!pv_fd_is_dev_null(nullfd, true)) { + exit(EXIT_FAILURE); + } if (dup2(nullfd, STDIN_FILENO) < 0) { pv_error("%s", "dup2"); exit(EXIT_FAILURE); diff --git a/src/pv/file.c b/src/pv/file.c index adf5762..a5bf528 100644 --- a/src/pv/file.c +++ b/src/pv/file.c @@ -18,11 +18,72 @@ #include #include #include +#include /*@-type@*/ /* splint has trouble with off_t and mode_t throughout this file. */ +/* + * Check that a file descriptor is open on /dev/null, returning true if so. + * Otherwise return false; if the second argument is true, reports the + * problem with pv_error() before returning. + * + * No error is reported if the file descriptor is less than zero. + */ +bool pv_fd_is_dev_null(int fd, bool report_error) +{ + struct stat sb; + struct utsname uts; + unsigned long expected_rdev; + bool is_dev_null; + + if (fd < 0) + return false; + + memset(&sb, 0, sizeof(sb)); + if (fstat(fd, &sb) < 0) { + if (report_error) + pv_perror("%s", "/dev/null"); + return false; + } + + expected_rdev = 0x0103; /* device 1,3 on Linux. */ + + is_dev_null = true; + if (!S_ISCHR(sb.st_mode)) { + debug("%s", "/dev/null: not a character device"); + is_dev_null = false; + } + + memset(&uts, 0, sizeof(uts)); + if (uname(&uts) >= 0) { + if (0 == strncmp(uts.sysname, "OpenBSD", 7)) { + expected_rdev = 0x0202; + } else if (0 == strncmp(uts.sysname, "FreeBSD", 7)) { + expected_rdev = 0x0022; + } else if (0 == strncmp(uts.sysname, "NetBSD", 6)) { + expected_rdev = 0x0202; + } else if (0 == strncmp(uts.sysname, "Darwin", 6)) { + expected_rdev = 0x03000002; + } + } + + if ((unsigned long) (sb.st_rdev) != expected_rdev) { + debug("/dev/null: fd is %08lx, expected %08lx", sb.st_rdev, expected_rdev); + is_dev_null = false; + } + + if (is_dev_null) + return true; + + if (report_error) + pv_error("%s", _("/dev/null is not usable")); + + return false; +} + + /* * Calculate the total number of bytes to be transferred by adding up the * sizes of all input files. If any of the input files are of indeterminate @@ -481,6 +542,10 @@ int pv_next_file(pvstate_t state, unsigned int filenum, int oldfd) (void) close(fd); fd = -1; } + if (!pv_fd_is_dev_null(state->transfer.discard_fd, true)) { + (void) close(fd); + fd = -1; + } } #endif /* HAVE_SPLICE */