See the attached patch. This one is adjusted a little from the one tested in
the PR:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118935
Regression tested. I have copied Huang to retest there as well.
Huang, please confirm.
OK for mainline?
Regards,
Jerry
---
libgfortran: [PR118935] Fix data race on the unit stream pointer
find_file0 walks the unit tree holding only unit_rwlock for reading,
and reads and dereferences each unit's stream pointer. new_unit,
set_internal_unit and the close paths change that pointer holding
only the unit's mutex, so find_file0 can see a stale or NULL stream,
or one already freed by a concurrent CLOSE.
Keep the file's device and inode in gfc_unit, stored before the
stream pointer is published with release semantics. find_file0
loads the pointer with acquire semantics and compares the stored
id, so it no longer dereferences the stream. All writes of the
stream pointer go through the new accessors.
Assisted-by: Claude Opus 5.5
PR libfortran/118935
libgfortran/ChangeLog:
* io/io.h (gfc_unit): Add file_dev and file_ino.
(set_unit_stream, get_unit_stream, set_unit_file_id)
(unit_file_id_matches): New functions.
* io/open.c (new_unit): Use connect_unit_stream.
(already_open): Use set_unit_stream.
* io/transfer.c (finalize_transfer): Likewise.
* io/unit.c (set_internal_unit): Use connect_unit_stream.
(init_units): Likewise.
* io/unix.c (connect_unit_stream): New function.
(find_file0): Use get_unit_stream and unit_file_id_matches
instead of dereferencing the stream.
* io/unix.h (connect_unit_stream): Declare.
---From cba182b175a0f6ed02bc3865064fbf8f040600c0 Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Tue, 3 Nov 2026 07:20:35 -0800
Subject: [PATCH] libgfortran: [PR118935] Fix data race on the unit stream
pointer
find_file0 walks the unit tree holding only unit_rwlock for reading,
and reads and dereferences each unit's stream pointer. new_unit,
set_internal_unit and the close paths change that pointer holding
only the unit's mutex, so find_file0 can see a stale or NULL stream,
or one already freed by a concurrent CLOSE.
Keep the file's device and inode in gfc_unit, stored before the
stream pointer is published with release semantics. find_file0
loads the pointer with acquire semantics and compares the stored
id, so it no longer dereferences the stream. All writes of the
stream pointer go through the new accessors.
Assisted-by: Claude Opus 5.5
PR libfortran/118935
libgfortran/ChangeLog:
* io/io.h (gfc_unit): Add file_dev and file_ino.
(set_unit_stream, get_unit_stream, set_unit_file_id)
(unit_file_id_matches): New functions.
* io/open.c (new_unit): Use connect_unit_stream.
(already_open): Use set_unit_stream.
* io/transfer.c (finalize_transfer): Likewise.
* io/unit.c (set_internal_unit): Use connect_unit_stream.
(init_units): Likewise.
* io/unix.c (connect_unit_stream): New function.
(find_file0): Use get_unit_stream and unit_file_id_matches
instead of dereferencing the stream.
* io/unix.h (connect_unit_stream): Declare.
---
libgfortran/io/io.h | 50 +++++++++++++++++++++++++++++++++++++++
libgfortran/io/open.c | 6 ++---
libgfortran/io/transfer.c | 2 +-
libgfortran/io/unit.c | 16 +++++++------
libgfortran/io/unix.c | 28 +++++++++++++++-------
libgfortran/io/unix.h | 3 +++
6 files changed, 86 insertions(+), 19 deletions(-)
diff --git a/libgfortran/io/io.h b/libgfortran/io/io.h
index ca58abd4108..d80033fd957 100644
--- a/libgfortran/io/io.h
+++ b/libgfortran/io/io.h
@@ -656,6 +656,8 @@ typedef struct gfc_unit
int unit_number;
stream *s;
+ uint64_t file_dev, file_ino;
+
/* Treap links. */
struct gfc_unit *left, *right;
int priority;
@@ -1021,6 +1023,54 @@ internal_proto(size_from_complex_kind);
extern void free_ionml (st_parameter_dt *);
internal_proto(free_ionml);
+/* Accessors for u->s. */
+
+static inline void
+set_unit_stream (gfc_unit *u, stream *s)
+{
+#ifdef HAVE_ATOMIC_FETCH_ADD
+ __atomic_store_n (&u->s, s, __ATOMIC_RELEASE);
+#else
+ u->s = s;
+#endif
+}
+
+static inline stream *
+get_unit_stream (gfc_unit *u)
+{
+#ifdef HAVE_ATOMIC_FETCH_ADD
+ return __atomic_load_n (&u->s, __ATOMIC_ACQUIRE);
+#else
+ return u->s;
+#endif
+}
+
+/* Record the unit's file identity. Must be called before the matching
+ set_unit_stream. */
+
+static inline void
+set_unit_file_id (gfc_unit *u, uint64_t dev, uint64_t ino)
+{
+#ifdef HAVE_ATOMIC_FETCH_ADD
+ __atomic_store_n (&u->file_dev, dev, __ATOMIC_RELAXED);
+ __atomic_store_n (&u->file_ino, ino, __ATOMIC_RELAXED);
+#else
+ u->file_dev = dev;
+ u->file_ino = ino;
+#endif
+}
+
+static inline bool
+unit_file_id_matches (gfc_unit *u, uint64_t dev, uint64_t ino)
+{
+#ifdef HAVE_ATOMIC_FETCH_ADD
+ return (__atomic_load_n (&u->file_dev, __ATOMIC_RELAXED) == dev
+ && __atomic_load_n (&u->file_ino, __ATOMIC_RELAXED) == ino);
+#else
+ return u->file_dev == dev && u->file_ino == ino;
+#endif
+}
+
static inline void
inc_waiting_locked (gfc_unit *u)
{
diff --git a/libgfortran/io/open.c b/libgfortran/io/open.c
index f2a2fcc26b9..76ae55f27b0 100644
--- a/libgfortran/io/open.c
+++ b/libgfortran/io/open.c
@@ -585,7 +585,7 @@ new_unit (st_parameter_open *opp, gfc_unit *u, unit_flags *flags)
if (u->unit_number != opp->common.unit)
internal_error (&opp->common, "Unit number changed");
- u->s = s;
+ connect_unit_stream (u, s);
u->flags = *flags;
u->read_bad = 0;
u->endfile = NO_ENDFILE;
@@ -730,8 +730,8 @@ already_open (st_parameter_open *opp, gfc_unit *u, unit_flags *flags)
return;
}
- u->s = NULL;
-
+ set_unit_stream (u, NULL);
+
#if !HAVE_UNLINK_OPEN_FILE
if (u->filename && u->flags.status == STATUS_SCRATCH)
remove (u->filename);
diff --git a/libgfortran/io/transfer.c b/libgfortran/io/transfer.c
index 95ead6936f2..618c7ef8b60 100644
--- a/libgfortran/io/transfer.c
+++ b/libgfortran/io/transfer.c
@@ -4586,7 +4586,7 @@ finalize_transfer (st_parameter_dt *dtp)
&& dtp->u.p.current_unit->s)
{
sclose (dtp->u.p.current_unit->s);
- dtp->u.p.current_unit->s = NULL;
+ set_unit_stream (dtp->u.p.current_unit, NULL);
}
}
diff --git a/libgfortran/io/unit.c b/libgfortran/io/unit.c
index 0a061a5f426..a79ae9d373a 100644
--- a/libgfortran/io/unit.c
+++ b/libgfortran/io/unit.c
@@ -660,12 +660,14 @@ set_internal_unit (st_parameter_dt *dtp, gfc_unit *iunit, int kind)
}
/* Set initial values for unit parameters. */
+ stream *s;
if (kind == 4)
- iunit->s = open_internal4 (iunit->internal_unit - start_record,
- iunit->internal_unit_len, -start_record);
+ s = open_internal4 (iunit->internal_unit - start_record,
+ iunit->internal_unit_len, -start_record);
else
- iunit->s = open_internal (iunit->internal_unit - start_record,
- iunit->internal_unit_len, -start_record);
+ s = open_internal (iunit->internal_unit - start_record,
+ iunit->internal_unit_len, -start_record);
+ connect_unit_stream (iunit, s);
iunit->bytes_left = iunit->recl;
iunit->last_record=0;
@@ -787,7 +789,7 @@ init_units (void)
if (options.stdin_unit >= 0)
{ /* STDIN */
u = insert_unit (options.stdin_unit);
- u->s = input_stream ();
+ connect_unit_stream (u, input_stream ());
u->flags.action = ACTION_READ;
@@ -819,7 +821,7 @@ init_units (void)
if (options.stdout_unit >= 0)
{ /* STDOUT */
u = insert_unit (options.stdout_unit);
- u->s = output_stream ();
+ connect_unit_stream (u, output_stream ());
u->flags.action = ACTION_WRITE;
@@ -850,7 +852,7 @@ init_units (void)
if (options.stderr_unit >= 0)
{ /* STDERR */
u = insert_unit (options.stderr_unit);
- u->s = error_stream ();
+ connect_unit_stream (u, error_stream ());
u->flags.action = ACTION_WRITE;
diff --git a/libgfortran/io/unix.c b/libgfortran/io/unix.c
index 333fa6547fa..b5d37eccab9 100644
--- a/libgfortran/io/unix.c
+++ b/libgfortran/io/unix.c
@@ -1704,6 +1704,18 @@ compare_file_filename (gfc_unit *u, const char *name, gfc_charlen_type len)
# define FIND_FILE0_ARGS id, path
#endif
+/* connect_unit_stream()-- Attach the stream to the unit. */
+
+void
+connect_unit_stream (gfc_unit *u, stream *s)
+{
+ unix_stream *us = (unix_stream *) s;
+
+ set_unit_file_id (u, (uint64_t) us->st_dev, (uint64_t) us->st_ino);
+ set_unit_stream (u, s);
+}
+
+
/* find_file0()-- Recursive work function for find_file() */
static gfc_unit *
@@ -1718,15 +1730,15 @@ find_file0 (gfc_unit *u, FIND_FILE0_DECL)
return NULL;
#ifdef HAVE_WORKING_STAT
- if (u->s != NULL)
- {
- unix_stream *s = (unix_stream *) (u->s);
- if (st[0].st_dev == s->st_dev && st[0].st_ino == s->st_ino)
- return u;
- }
+ /* Match a connected unit by its file id. */
+ if (get_unit_stream (u) != NULL
+ && unit_file_id_matches (u, (uint64_t) st[0].st_dev,
+ (uint64_t) st[0].st_ino))
+ return u;
#else
-# ifdef __MINGW32__
- if (u->s && ((id1 = id_from_fd (((unix_stream *) u->s)->fd)) || id1))
+# ifdef __MINGW32__
+ unix_stream *s = (unix_stream *) get_unit_stream (u);
+ if (s && ((id1 = id_from_fd (s->fd)) || id1))
{
if (id == id1)
return u;
diff --git a/libgfortran/io/unix.h b/libgfortran/io/unix.h
index 8680df6d70b..b8afa08b030 100644
--- a/libgfortran/io/unix.h
+++ b/libgfortran/io/unix.h
@@ -138,6 +138,9 @@ internal_proto(error_stream);
extern int compare_file_filename (gfc_unit *, const char *, gfc_charlen_type);
internal_proto(compare_file_filename);
+extern void connect_unit_stream (gfc_unit *, stream *);
+internal_proto(connect_unit_stream);
+
extern gfc_unit *find_file (const char *file, gfc_charlen_type file_len);
internal_proto(find_file);
--
2.56.0