mailing list of musl libc
 help / color / mirror / code / Atom feed
* [musl] [PATCH] stdio: don't write empty iovec first
@ 2025-04-23 11:05 Casey Connolly
  0 siblings, 0 replies; only message in thread
From: Casey Connolly @ 2025-04-23 11:05 UTC (permalink / raw)
  To: musl; +Cc: Casey Connolly

From: Casey Connolly <casey@postmarketos.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@postmarketos.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


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-04-23 11:06 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-23 11:05 [musl] [PATCH] stdio: don't write empty iovec first Casey Connolly

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).