aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorCalvin Rose <calsrose@gmail.com>2026-03-01 10:27:32 -0600
committerCalvin Rose <calsrose@gmail.com>2026-03-01 10:39:50 -0600
commit32d75c9e498e729e5c02433b8ec7084ccf2bbb95 (patch)
treed3d809fc7085b66f3c7ff95f3d330b2ee5c8a2b2
parentMove some files around code more defensively for mitigation. (diff)
Dup io file descriptors when marshalling closable files.
For unclosable files, no need to dup, but for closable files we can get a resource leak. Valgrind and similar tools won't catch this but IO will unexpectedly start going to the wrong descriptor if a file was transferred to a new thread, closed, and then a new file was created.
-rw-r--r--src/boot/boot.janet5
-rw-r--r--src/core/io.c32
-rw-r--r--src/include/janet.h1
-rw-r--r--test/suite-ev2.janet17
4 files changed, 45 insertions, 10 deletions
diff --git a/src/boot/boot.janet b/src/boot/boot.janet
index 370a774f..8d624e52 100644
--- a/src/boot/boot.janet
+++ b/src/boot/boot.janet
@@ -4014,7 +4014,7 @@
"handler not supported for :datagram servers")
(def s (net/listen host port type no-reuse))
(if handler
- (ev/go (fn [] (net/accept-loop s handler))))
+ (ev/go (fn :net/server-handler [] (net/accept-loop s handler))))
s))
###
@@ -4670,8 +4670,7 @@
(defn- run-main
[env subargs arg]
- (when-let [entry (in env 'main)
- main (or (get entry :value) (in (get entry :ref) 0))]
+ (when-let [main (module/value env 'main)]
(def guard (if (get env :debug) :ydt :y))
(defn wrap-main [&]
(main ;subargs))
diff --git a/src/core/io.c b/src/core/io.c
index 43996526..274f9eeb 100644
--- a/src/core/io.c
+++ b/src/core/io.c
@@ -109,10 +109,11 @@ static int32_t checkflags(const uint8_t *str) {
return flags;
}
-static void *makef(FILE *f, int32_t flags) {
+static void *makef(FILE *f, int32_t flags, size_t bufsize) {
JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile));
iof->file = f;
iof->flags = flags;
+ iof->vbufsize = bufsize;
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
/* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is
* not standard. */
@@ -164,6 +165,7 @@ JANET_CORE_FN(cfun_io_fopen,
flags = JANET_FILE_READ;
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
+ size_t bufsize = BUFSIZ;
if (f != NULL) {
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
struct stat st;
@@ -173,7 +175,7 @@ JANET_CORE_FN(cfun_io_fopen,
janet_panicf("cannot open directory: %s", fname);
}
#endif
- size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
+ bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);
if (result) {
@@ -181,7 +183,7 @@ JANET_CORE_FN(cfun_io_fopen,
}
}
}
- return f ? janet_makefile(f, flags)
+ return f ? janet_wrap_abstract(makef(f, flags, bufsize))
: (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil())
: janet_wrap_nil();
}
@@ -410,12 +412,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) {
JanetFile *iof = (JanetFile *)p;
if (ctx->flags & JANET_MARSHAL_UNSAFE) {
janet_marshal_abstract(ctx, p);
+ int fno = -1;
#ifdef JANET_WINDOWS
- janet_marshal_int(ctx, _fileno(iof->file));
+ if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
+ fno = _fileno(iof->file);
+ } else {
+ fno = _dup(_fileno(iof->file));
+ }
#else
- janet_marshal_int(ctx, fileno(iof->file));
+ if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
+ fno = fileno(iof->file);
+ } else {
+ fno = dup(fileno(iof->file));
+ }
#endif
+ janet_marshal_int(ctx, fno);
janet_marshal_int(ctx, iof->flags);
+ janet_marshal_size(ctx, iof->vbufsize);
} else {
janet_panic("cannot marshal file in safe mode");
}
@@ -444,6 +457,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) {
} else {
iof->flags = flags;
}
+ iof->vbufsize = janet_unmarshal_size(ctx);
+ if (iof->vbufsize != BUFSIZ) {
+ int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize);
+ janet_assert(!result, "unmarshal setvbuf");
+ }
return iof;
} else {
janet_panic("cannot unmarshal file in safe mode");
@@ -785,11 +803,11 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) {
}
JanetFile *janet_makejfile(FILE *f, int32_t flags) {
- return makef(f, flags);
+ return makef(f, flags, BUFSIZ);
}
Janet janet_makefile(FILE *f, int32_t flags) {
- return janet_wrap_abstract(makef(f, flags));
+ return janet_wrap_abstract(makef(f, flags, BUFSIZ));
}
JanetAbstract janet_checkfile(Janet j) {
diff --git a/src/include/janet.h b/src/include/janet.h
index e1a5c79a..ac2e3019 100644
--- a/src/include/janet.h
+++ b/src/include/janet.h
@@ -1281,6 +1281,7 @@ typedef struct JanetFile JanetFile;
struct JanetFile {
FILE *file;
int32_t flags;
+ size_t vbufsize;
};
/* For janet_try and janet_restore */
diff --git a/test/suite-ev2.janet b/test/suite-ev2.janet
index c94a2d86..caa323a4 100644
--- a/test/suite-ev2.janet
+++ b/test/suite-ev2.janet
@@ -84,4 +84,21 @@
(assert-error "cannot schedule non-new fiber"
(ev/go f))
+# IO file copying
+(os/mkdir "tmp")
+(def f-original (file/open "tmp/out.txt" :wb))
+(xprin f-original "hello\n")
+(file/flush f-original)
+(ev/do-thread
+ # Closes a COPY of the original file, otherwise we get a user-after-close file descriptor
+ (:close f-original))
+(def g-original (file/open "tmp/out2.txt" :wb))
+(xprin g-original "world1\n")
+(xprin f-original "world2\n")
+(:close f-original)
+(xprin g-original "abc\n")
+(:close g-original)
+(assert (deep= @"hello\nworld2\n" (slurp "tmp/out.txt")) "file threading 1")
+(assert (deep= @"world1\nabc\n" (slurp "tmp/out2.txt")) "file threading 2")
+
(end-suite)