diff mbox series

Fortran: Fix libgfortran I/O race with newunit_free [PR99529]

Message ID bbc7dd2f-02e7-482b-2abf-5fa3e4f06b47@net-b.de
State New
Headers show
Series Fortran: Fix libgfortran I/O race with newunit_free [PR99529] | expand

Commit Message

Tobias Burnus March 11, 2021, 9:42 a.m. UTC
Hi all,

as found by Martin (thanks!) there is a race for newunit_free.
While that call is within the unitlock for the calls in io/unit.c,
the call in transfer.c did not use locks.

Additionally,
       unit = get_gfc_unit (dtp->common.unit, do_create);
       set_internal_unit (dtp, unit, kind);
gets first the unit (with proper locking when using the unit number
dtp->common.unit) but then in set_internal_unit it re-sets the
unit number to the same number without locking. That causes
race warnings and if the assignment is not atomic it is a true race.

OK for mainline? What about GCC 10?

As Martin notes in the email thread and in the PR there are more
race warnings (and likely true race issues).

Tobias

Comments

Tobias Burnus March 11, 2021, 10:38 a.m. UTC | #1
Revised version – the previous had a lock inversion, which could lead to 
a deadlock, found by -fsanitize=thread. See transfer.c for the changes.

OK?

Tobias

On 11.03.21 10:42, Tobias Burnus wrote:
> Hi all,
>
> as found by Martin (thanks!) there is a race for newunit_free.
> While that call is within the unitlock for the calls in io/unit.c,
> the call in transfer.c did not use locks.
>
> Additionally,
>       unit = get_gfc_unit (dtp->common.unit, do_create);
>       set_internal_unit (dtp, unit, kind);
> gets first the unit (with proper locking when using the unit number
> dtp->common.unit) but then in set_internal_unit it re-sets the
> unit number to the same number without locking. That causes
> race warnings and if the assignment is not atomic it is a true race.
>
> OK for mainline? What about GCC 10?
>
> As Martin notes in the email thread and in the PR there are more
> race warnings (and likely true race issues).
>
> Tobias
>
Jerry DeLisle March 12, 2021, 1:35 a.m. UTC | #2
Looks good Tobias, OK,

Odd about that line in set_internal_unit. Probably leftover from a 
copy/paste. I see in comment #5 of the PR that you mentioned trying the 
assert to make sure it is useless code.

Thanks for the patch,

Jerry

On 3/11/21 2:38 AM, Tobias Burnus wrote:
> Revised version – the previous had a lock inversion, which could lead 
> to a deadlock, found by -fsanitize=thread. See transfer.c for the 
> changes.
>
> OK?
>
> Tobias
>
> On 11.03.21 10:42, Tobias Burnus wrote:
>> Hi all,
>>
>> as found by Martin (thanks!) there is a race for newunit_free.
>> While that call is within the unitlock for the calls in io/unit.c,
>> the call in transfer.c did not use locks.
>>
>> Additionally,
>>       unit = get_gfc_unit (dtp->common.unit, do_create);
>>       set_internal_unit (dtp, unit, kind);
>> gets first the unit (with proper locking when using the unit number
>> dtp->common.unit) but then in set_internal_unit it re-sets the
>> unit number to the same number without locking. That causes
>> race warnings and if the assignment is not atomic it is a true race.
>>
>> OK for mainline? What about GCC 10?
>>
>> As Martin notes in the email thread and in the PR there are more
>> race warnings (and likely true race issues).
>>
>> Tobias
>>
diff mbox series

Patch

Fortran: Fix libgfortran I/O race with newunit_free [PR99529]

libgfortran/ChangeLog:

	* io/transfer.c (st_read_done_worker, st_write_done_worker):
	Add unit_lock lock/unlock around newunit_free call.
	* io/unit.c (set_internal_unit): Don't reset the unit_number
	to the same number as this cause race warnings.

diff --git a/libgfortran/io/transfer.c b/libgfortran/io/transfer.c
index 27bee9d4e01..a6f115d5803 100644
--- a/libgfortran/io/transfer.c
+++ b/libgfortran/io/transfer.c
@@ -4358,7 +4358,9 @@  st_read_done_worker (st_parameter_dt *dtp)
 		free (dtp->u.p.current_unit->ls);
 	      dtp->u.p.current_unit->ls = NULL;
 	    }
+	  LOCK (&unit_lock);
 	  newunit_free (dtp->common.unit);
+	  UNLOCK (&unit_lock);
 	}
       if (dtp->u.p.unit_is_internal || dtp->u.p.format_not_saved)
 	{
@@ -4446,7 +4448,9 @@  st_write_done_worker (st_parameter_dt *dtp)
 		free (dtp->u.p.current_unit->ls);
 	      dtp->u.p.current_unit->ls = NULL;
 	    }
+	  LOCK (&unit_lock);
 	  newunit_free (dtp->common.unit);
+	  UNLOCK (&unit_lock);
 	}
       if (dtp->u.p.unit_is_internal || dtp->u.p.format_not_saved)
 	{
diff --git a/libgfortran/io/unit.c b/libgfortran/io/unit.c
index 2ea664331bc..b0cc6ab2301 100644
--- a/libgfortran/io/unit.c
+++ b/libgfortran/io/unit.c
@@ -456,7 +456,6 @@  set_internal_unit (st_parameter_dt *dtp, gfc_unit *iunit, int kind)
 {
   gfc_offset start_record = 0;
 
-  iunit->unit_number = dtp->common.unit;
   iunit->recl = dtp->internal_unit_len;
   iunit->internal_unit = dtp->internal_unit;
   iunit->internal_unit_len = dtp->internal_unit_len;