musl: stdio: check if stream is writable earlier in fputs() and friends

This commit is contained in:
Yu Watanabe
2025-11-15 05:10:33 +09:00
parent 08d207ba73
commit 384e88a238
4 changed files with 103 additions and 0 deletions
+18
View File
@@ -11,3 +11,21 @@
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
+36
View 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);
+1
View 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',
+48
View File
@@ -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);