]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
musl: stdio: check if stream is writable earlier in fputs() and friends
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 13 Nov 2025 04:40:19 +0000 (13:40 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 14 Nov 2025 20:10:33 +0000 (05:10 +0900)
src/include/musl/stdio.h
src/libc/musl/stdio.c
src/test/meson.build
src/test/test-stdio.c [new file with mode: 0644]

index d677201f457bb9a019cdedadcab28d787ae4e1ad..2ff95e952a4c26e33343eceb19c8526bb9680792 100644 (file)
 int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags);
 #  define renameat2 missing_renameat2
 #endif
+
+/* When a stream is opened read-only under glibc, fputs() and friends fail with EBADF. However, they
+ * succeed under musl. We rely on the glibc behavior in the code base. The following _check_writable()
+ * functions first check if the passed stream is writable, and refuse to write with EBADF if not. */
+
+int putc_check_writable(int c, FILE *stream);
+int putc_unlocked_check_writable(int c, FILE *stream);
+int fputc_check_writable(int c, FILE *stream);
+int fputc_unlocked_check_writable(int c, FILE *stream);
+int fputs_check_writable(const char *s, FILE *stream);
+int fputs_unlocked_check_writable(const char *s, FILE *stream);
+
+#define putc           putc_check_writable
+#define putc_unlocked  putc_unlocked_check_writable
+#define fputc          fputc_check_writable
+#define fputc_unlocked fputc_unlocked_check_writable
+#define fputs          fputs_check_writable
+#define fputs_unlocked fputs_unlocked_check_writable
index 102a22cd5cd2302c37976b3bcc76a9a637084e9e..6d02aef5f65daae8e63c88aaa2d16d3181056e85 100644 (file)
@@ -1,6 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <errno.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <sys/syscall.h>
 #include <unistd.h>
 
@@ -9,3 +11,37 @@ int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *_
         return syscall(__NR_renameat2, __oldfd, __old, __newfd, __new, __flags);
 }
 #endif
+
+#define DEFINE_PUT(func)                                         \
+        int func##_check_writable(int c, FILE *stream) {         \
+                if (!__fwritable(stream)) {                      \
+                        errno = EBADF;                           \
+                        return EOF;                              \
+                }                                                \
+                                                                 \
+                return func(c, stream);                          \
+        }
+
+#define DEFINE_FPUTS(func)                                       \
+        int func##_check_writable(const char *s, FILE *stream) { \
+                if (!__fwritable(stream)) {                      \
+                        errno = EBADF;                           \
+                        return EOF;                              \
+                }                                                \
+                                                                 \
+                return func(s, stream);                          \
+        }
+
+#undef putc
+#undef putc_unlocked
+#undef fputc
+#undef fputc_unlocked
+#undef fputs
+#undef fputs_unlocked
+
+DEFINE_PUT(putc);
+DEFINE_PUT(putc_unlocked);
+DEFINE_PUT(fputc);
+DEFINE_PUT(fputc_unlocked);
+DEFINE_FPUTS(fputs);
+DEFINE_FPUTS(fputs_unlocked);
index b46197e4b73420537a416aacc8bdca4a80db7577..9f89ce8e58a20978dece8acb9004cb13716a39a0 100644 (file)
@@ -191,6 +191,7 @@ simple_tests += files(
         'test-specifier.c',
         'test-stat-util.c',
         'test-static-destruct.c',
+        'test-stdio.c',
         'test-strbuf.c',
         'test-string-util.c',
         'test-strip-tab-ansi.c',
diff --git a/src/test/test-stdio.c b/src/test/test-stdio.c
new file mode 100644 (file)
index 0000000..8dd1928
--- /dev/null
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdio.h>
+
+#include "fd-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+
+TEST(read_only) {
+        _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-stdio-read-only-XXXXXX";
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        ASSERT_OK(fd = mkostemp_safe(fn));
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = putc('a', f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = putc_unlocked('a', f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = fputc('a', f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = fputc_unlocked('a', f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = fputs("a", f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+
+        ASSERT_NOT_NULL(f = fopen(fn, "r"));
+        ASSERT_ERROR_ERRNO(r = fputs_unlocked("a", f), EBADF);
+        ASSERT_EQ(r, EOF);
+        f = safe_fclose(f);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);