Skip to content

Commit 3a68a17

Browse files
fix: race conditions in FileIO
1 parent d986124 commit 3a68a17

1 file changed

Lines changed: 64 additions & 28 deletions

File tree

Modules/_io/fileio.c

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,38 @@ fileio_dealloc_warn(PyObject *op, PyObject *source)
117117
Py_RETURN_NONE;
118118
}
119119

120+
/* Atomic accessors for self->fd.
121+
* Relaxed ordering is sufficient: the OS syscall that follows provides the
122+
* real barrier, and we only need to prevent data-race UB on the int-sized field itself. */
123+
static inline int
124+
fileio_get_fd(fileio *self)
125+
{
126+
return _Py_atomic_load_int_relaxed(&self->fd);
127+
}
128+
129+
static inline void
130+
fileio_set_fd(fileio *self, int fd)
131+
{
132+
_Py_atomic_store_int_relaxed(&self->fd, fd);
133+
}
134+
135+
/* Atomically replace self->fd with -1 and return the old value.
136+
* Used by internal_close so that two concurrent calls to close cannot
137+
* both observe fd >= 0 and both attempt to close the same descriptor. */
138+
static inline int
139+
fileio_exchange_fd(fileio *self)
140+
{
141+
return _Py_atomic_exchange_int(&self->fd, -1);
142+
}
143+
120144
/* Returns 0 on success, -1 with exception set on failure. */
121145
static int
122146
internal_close(fileio *self)
123147
{
124148
int err = 0;
125149
int save_errno = 0;
126-
if (self->fd >= 0) {
127-
int fd = self->fd;
128-
self->fd = -1;
150+
int fd = fileio_exchange_fd(self);
151+
if (fd >= 0) {
129152
/* fd is accessible and someone else may have closed it */
130153
Py_BEGIN_ALLOW_THREADS
131154
_Py_BEGIN_SUPPRESS_IPH
@@ -167,7 +190,7 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls)
167190
res = PyObject_CallMethodOneArg((PyObject*)state->PyRawIOBase_Type,
168191
&_Py_ID(close), (PyObject *)self);
169192
if (!self->closefd) {
170-
self->fd = -1;
193+
fileio_set_fd(self, -1);
171194
return res;
172195
}
173196

@@ -609,9 +632,10 @@ static PyObject *
609632
_io_FileIO_fileno_impl(fileio *self)
610633
/*[clinic end generated code: output=a9626ce5398ece90 input=0b9b2de67335ada3]*/
611634
{
612-
if (self->fd < 0)
635+
int fd = fileio_get_fd(self);
636+
if (fd < 0)
613637
return err_closed();
614-
return PyLong_FromLong((long) self->fd);
638+
return PyLong_FromLong((long) fd);
615639
}
616640

617641
/*[clinic input]
@@ -624,7 +648,7 @@ static PyObject *
624648
_io_FileIO_readable_impl(fileio *self)
625649
/*[clinic end generated code: output=640744a6150fe9ba input=a3fdfed6eea721c5]*/
626650
{
627-
if (self->fd < 0)
651+
if (fileio_get_fd(self) < 0)
628652
return err_closed();
629653
return PyBool_FromLong((long) self->readable);
630654
}
@@ -639,7 +663,7 @@ static PyObject *
639663
_io_FileIO_writable_impl(fileio *self)
640664
/*[clinic end generated code: output=96cefc5446e89977 input=c204a808ca2e1748]*/
641665
{
642-
if (self->fd < 0)
666+
if (fileio_get_fd(self) < 0)
643667
return err_closed();
644668
return PyBool_FromLong((long) self->writable);
645669
}
@@ -654,7 +678,7 @@ static PyObject *
654678
_io_FileIO_seekable_impl(fileio *self)
655679
/*[clinic end generated code: output=47909ca0a42e9287 input=c8e5554d2fd63c7f]*/
656680
{
657-
if (self->fd < 0)
681+
if (fileio_get_fd(self) < 0)
658682
return err_closed();
659683
if (self->seekable < 0) {
660684
/* portable_lseek() sets the seekable attribute */
@@ -686,14 +710,16 @@ _io_FileIO_readinto_impl(fileio *self, PyTypeObject *cls, Py_buffer *buffer)
686710
Py_ssize_t n;
687711
int err;
688712

689-
if (self->fd < 0)
713+
int fd;
714+
fd = fileio_get_fd(self);
715+
if (fd < 0)
690716
return err_closed();
691717
if (!self->readable) {
692718
_PyIO_State *state = get_io_state_by_cls(cls);
693719
return err_mode(state, "reading");
694720
}
695721

696-
n = _Py_read(self->fd, buffer->buf, buffer->len);
722+
n = _Py_read(fd, buffer->buf, buffer->len);
697723
/* copy errno because PyBuffer_Release() can indirectly modify it */
698724
err = errno;
699725

@@ -754,7 +780,9 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
754780
Py_ssize_t n;
755781
size_t bufsize;
756782

757-
if (self->fd < 0) {
783+
int fd;
784+
fd = fileio_get_fd(self);
785+
if (fd < 0) {
758786
return err_closed();
759787
}
760788
if (!self->readable) {
@@ -795,9 +823,9 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
795823
Py_BEGIN_ALLOW_THREADS
796824
_Py_BEGIN_SUPPRESS_IPH
797825
#ifdef MS_WINDOWS
798-
pos = _lseeki64(self->fd, 0L, SEEK_CUR);
826+
pos = _lseeki64(fd, 0L, SEEK_CUR);
799827
#else
800-
pos = lseek(self->fd, 0L, SEEK_CUR);
828+
pos = lseek(fd, 0L, SEEK_CUR);
801829
#endif
802830
_Py_END_SUPPRESS_IPH
803831
Py_END_ALLOW_THREADS
@@ -830,7 +858,7 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
830858
}
831859
}
832860

833-
n = _Py_read(self->fd,
861+
n = _Py_read(fd,
834862
(char*)PyBytesWriter_GetData(writer) + bytes_read,
835863
bufsize - bytes_read);
836864

@@ -875,7 +903,9 @@ static PyObject *
875903
_io_FileIO_read_impl(fileio *self, PyTypeObject *cls, Py_ssize_t size)
876904
/*[clinic end generated code: output=bbd749c7c224143e input=c7baa3b440af9337]*/
877905
{
878-
if (self->fd < 0)
906+
int fd;
907+
fd = fileio_get_fd(self);
908+
if (fd < 0)
879909
return err_closed();
880910
if (!self->readable) {
881911
_PyIO_State *state = get_io_state_by_cls(cls);
@@ -895,7 +925,7 @@ _io_FileIO_read_impl(fileio *self, PyTypeObject *cls, Py_ssize_t size)
895925
}
896926
char *ptr = PyBytesWriter_GetData(writer);
897927

898-
Py_ssize_t n = _Py_read(self->fd, ptr, size);
928+
Py_ssize_t n = _Py_read(fd, ptr, size);
899929
if (n == -1) {
900930
// copy errno because PyBytesWriter_Discard() can indirectly modify it
901931
int err = errno;
@@ -930,14 +960,16 @@ _io_FileIO_write_impl(fileio *self, PyTypeObject *cls, Py_buffer *b)
930960
Py_ssize_t n;
931961
int err;
932962

933-
if (self->fd < 0)
963+
int fd;
964+
fd = fileio_get_fd(self);
965+
if (fd < 0)
934966
return err_closed();
935967
if (!self->writable) {
936968
_PyIO_State *state = get_io_state_by_cls(cls);
937969
return err_mode(state, "writing");
938970
}
939971

940-
n = _Py_write(self->fd, b->buf, b->len);
972+
n = _Py_write(fd, b->buf, b->len);
941973
/* copy errno because PyBuffer_Release() can indirectly modify it */
942974
err = errno;
943975

@@ -959,7 +991,8 @@ static PyObject *
959991
portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error)
960992
{
961993
Py_off_t pos, res;
962-
int fd = self->fd;
994+
int fd;
995+
fd = fileio_get_fd(self);
963996

964997
#ifdef SEEK_SET
965998
/* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */
@@ -1040,7 +1073,7 @@ static PyObject *
10401073
_io_FileIO_seek_impl(fileio *self, PyObject *pos, int whence)
10411074
/*[clinic end generated code: output=c976acdf054e6655 input=f165a1b4f5d494ad]*/
10421075
{
1043-
if (self->fd < 0)
1076+
if (fileio_get_fd(self) < 0)
10441077
return err_closed();
10451078

10461079
return portable_lseek(self, pos, whence, false);
@@ -1058,7 +1091,7 @@ static PyObject *
10581091
_io_FileIO_tell_impl(fileio *self)
10591092
/*[clinic end generated code: output=ffe2147058809d0b input=807e24ead4cec2f9]*/
10601093
{
1061-
if (self->fd < 0)
1094+
if (fileio_get_fd(self) < 0)
10621095
return err_closed();
10631096

10641097
return portable_lseek(self, NULL, 1, false);
@@ -1086,7 +1119,7 @@ _io_FileIO_truncate_impl(fileio *self, PyTypeObject *cls, PyObject *posobj)
10861119
int ret;
10871120
int fd;
10881121

1089-
fd = self->fd;
1122+
fd = fileio_get_fd(self);
10901123
if (fd < 0)
10911124
return err_closed();
10921125
if (!self->writable) {
@@ -1181,7 +1214,8 @@ fileio_repr(PyObject *op)
11811214
fileio *self = PyFileIO_CAST(op);
11821215
const char *type_name = Py_TYPE(self)->tp_name;
11831216

1184-
if (self->fd < 0) {
1217+
int fd = fileio_get_fd(self);
1218+
if (fd < 0) {
11851219
return PyUnicode_FromFormat("<%.100s [closed]>", type_name);
11861220
}
11871221

@@ -1193,7 +1227,7 @@ fileio_repr(PyObject *op)
11931227
if (nameobj == NULL) {
11941228
res = PyUnicode_FromFormat(
11951229
"<%.100s fd=%d mode='%s' closefd=%s>",
1196-
type_name, self->fd, mode_string(self), self->closefd ? "True" : "False");
1230+
type_name, fd, mode_string(self), self->closefd ? "True" : "False");
11971231
}
11981232
else {
11991233
int status = Py_ReprEnter((PyObject *)self);
@@ -1225,11 +1259,13 @@ _io_FileIO_isatty_impl(fileio *self)
12251259
{
12261260
long res;
12271261

1228-
if (self->fd < 0)
1262+
int fd;
1263+
fd = fileio_get_fd(self);
1264+
if (fd < 0)
12291265
return err_closed();
12301266
Py_BEGIN_ALLOW_THREADS
12311267
_Py_BEGIN_SUPPRESS_IPH
1232-
res = isatty(self->fd);
1268+
res = isatty(fd);
12331269
_Py_END_SUPPRESS_IPH
12341270
Py_END_ALLOW_THREADS
12351271
return PyBool_FromLong(res);
@@ -1281,7 +1317,7 @@ static PyObject *
12811317
fileio_get_closed(PyObject *op, void *closure)
12821318
{
12831319
fileio *self = PyFileIO_CAST(op);
1284-
return PyBool_FromLong((long)(self->fd < 0));
1320+
return PyBool_FromLong((long)(fileio_get_fd(self) < 0));
12851321
}
12861322

12871323
static PyObject *

0 commit comments

Comments
 (0)