|
|
Message-ID: <20250423110608.1050929-1-caleb@postmarketos.org>
Date: Wed, 23 Apr 2025 13:05:08 +0200
From: Casey Connolly <casey.connolly@...aro.org>
To: musl@...ts.openwall.com
Cc: Casey Connolly <casey@...tmarketos.org>
Subject: [PATCH] stdio: don't write empty iovec first
From: Casey Connolly <casey@...tmarketos.org>
When buffering on a FILE is disabled the first iovec will be empty, this
results in an empty write which usually has no effect, but when the
write is to a virtual filesystem like sysfs or procfs it will still call
into the kernel handlers.
In some cases like /proc/$PID/uid_map where a specific format is
expected the handler will return an error and the entire write will be
aborted.
Avoid this scenario by skipping the first iovec if it is empty.
Signed-off-by: Casey Connolly <casey@...tmarketos.org>
---
A simple reproducer for this using uid_map is as follows:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
int main(int argc, char **argv)
{
int ret, pid;
char path[64] = { 0 };
pid = fork();
/* Parent unshares the user namespace so that the child can set its uid_map */
if (pid) {
unshare(CLONE_NEWUSER);
sleep(2);
} else
sleep(1);
/* Write the uid_map as the child... */
snprintf(path, 64, "/proc/%u/uid_map", getpid());
FILE *f = fopen(path, "w");
/* This setvbuf() makes the write fail with -EINVAL!!! */
setvbuf(f, NULL, _IONBF, 0);
/* Now this called into __stdio_write() which creates two iovecs, the first is empty... */
// writev(3, [{iov_base="", iov_len=0}, {iov_base="0 10000 1\n", iov_len=10}], 2) = -1 EINVAL (Invalid argument)
ret = fputs("0 0 1\n", f);
return ret;
}
---
src/stdio/__stdio_write.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/stdio/__stdio_write.c b/src/stdio/__stdio_write.c
index d2d89475b0f9..89cb9917cca4 100644
--- a/src/stdio/__stdio_write.c
+++ b/src/stdio/__stdio_write.c
@@ -9,8 +9,14 @@ size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len)
};
struct iovec *iov = iovs;
size_t rem = iov[0].iov_len + iov[1].iov_len;
int iovcnt = 2;
+
+ /* Don't write an empty iovec, this can confuse some kernel APIs */
+ if (!iov->iov_len) {
+ iov++;
+ iovcnt = 1;
+ }
ssize_t cnt;
for (;;) {
cnt = syscall(SYS_writev, f->fd, iov, iovcnt);
if (cnt == rem) {
--
2.49.0
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.