Skip to content

Commit abf9167

Browse files
committed
Make PerlIO marginally reentrant
Currently if an operation on a file handle is interrupted, and if the signal handler accesses that same file handle (e.g. closes it), then perl will crash. See [perl #75556]. This commit provides some basic infrastructure to avoid segfaults. Basically it adds a lock count field to each handle (by re-purposing the unused flags field in the PL_perlio array), then each time a signal handler is called, the count is incremented. Then various parts of PerlIO use a positive count to change behaviour. Most importantly, when layers are popped, the PerlIOl structure is cleared, but not freed, and is left in the chain of layers. This means that callers still holding pointers to the various layers won't access freed structures. It does however mean that PerlIOl structs may be leaked, and possibly slots in PL_perlio. But this is better than crashing. Not much has been done to give sensible behaviour on re-entrancy; for example, a buffer that has already been written once might get written again. Fixing this sort of thing would require a large-scale audit of perlio.c.
1 parent cc6623a commit abf9167

File tree

6 files changed

+218
-16
lines changed

6 files changed

+218
-16
lines changed

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4481,6 +4481,7 @@ t/io/crlf.t See if :crlf works
44814481
t/io/crlf_through.t See if pipe passes data intact with :crlf
44824482
t/io/defout.t See if PL_defoutgv works
44834483
t/io/dup.t See if >& works right
4484+
t/io/eintr.t See if code called during EINTR is safe
44844485
t/io/errnosig.t Test case for restoration $! when leaving signal handlers
44854486
t/io/errno.t See if $! is correctly set
44864487
t/io/fflush.t See if auto-flush on fork/exec/system/qx works

perl.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*
99
*/
1010

11-
1211
#ifndef H_PERL
1312
#define H_PERL 1
1413

perlio.c

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
int mkstemp(char*);
7171
#endif
7272

73+
#define PerlIO_lockcnt(f) (((PerlIOl*)(f))->head->flags)
74+
7375
/* Call the callback or PerlIOBase, and return failure. */
7476
#define Perl_PerlIO_or_Base(f, callback, base, failure, args) \
7577
if (PerlIOValid(f)) { \
@@ -583,7 +585,7 @@ PerlIO_allocate(pTHX)
583585
last = (PerlIOl **) (f);
584586
for (i = 1; i < PERLIO_TABLE_SIZE; i++) {
585587
if (!((++f)->next)) {
586-
f->flags = 0;
588+
f->flags = 0; /* lockcnt */
587589
f->tab = NULL;
588590
f->head = f;
589591
return (PerlIO *)f;
@@ -595,7 +597,7 @@ PerlIO_allocate(pTHX)
595597
return NULL;
596598
}
597599
*last = (PerlIOl*) f++;
598-
f->flags = 0;
600+
f->flags = 0; /* lockcnt */
599601
f->tab = NULL;
600602
f->head = f;
601603
return (PerlIO*) f;
@@ -782,8 +784,16 @@ PerlIO_pop(pTHX_ PerlIO *f)
782784
if ((*l->tab->Popped) (aTHX_ f) != 0)
783785
return;
784786
}
785-
*f = l->next;
786-
Safefree(l);
787+
if (PerlIO_lockcnt(f)) {
788+
/* we're in use; defer freeing the structure */
789+
PerlIOBase(f)->flags = PERLIO_F_CLEARED;
790+
PerlIOBase(f)->tab = NULL;
791+
}
792+
else {
793+
*f = l->next;
794+
Safefree(l);
795+
}
796+
787797
}
788798
}
789799

@@ -1488,6 +1498,9 @@ Perl_PerlIO_close(pTHX_ PerlIO *f)
14881498
const int code = PerlIO__close(aTHX_ f);
14891499
while (PerlIOValid(f)) {
14901500
PerlIO_pop(aTHX_ f);
1501+
if (PerlIO_lockcnt(f))
1502+
/* we're in use; the 'pop' deferred freeing the structure */
1503+
f = PerlIONext(f);
14911504
}
14921505
return code;
14931506
}
@@ -2518,6 +2531,38 @@ typedef struct {
25182531
int oflags; /* open/fcntl flags */
25192532
} PerlIOUnix;
25202533

2534+
static void
2535+
S_lockcnt_dec(pTHX_ const void* f)
2536+
{
2537+
PerlIO_lockcnt((PerlIO*)f)--;
2538+
}
2539+
2540+
2541+
/* call the signal handler, and if that handler happens to clear
2542+
* this handle, free what we can and return true */
2543+
2544+
static bool
2545+
S_perlio_async_run(pTHX_ PerlIO* f) {
2546+
ENTER;
2547+
SAVEDESTRUCTOR_X(S_lockcnt_dec, (void*)f);
2548+
PerlIO_lockcnt(f)++;
2549+
PERL_ASYNC_CHECK();
2550+
if ( !(PerlIOBase(f)->flags & PERLIO_F_CLEARED) )
2551+
return 0;
2552+
/* we've just run some perl-level code that could have done
2553+
* anything, including closing the file or clearing this layer.
2554+
* If so, free any lower layers that have already been
2555+
* cleared, then return an error. */
2556+
while (PerlIOValid(f) &&
2557+
(PerlIOBase(f)->flags & PERLIO_F_CLEARED))
2558+
{
2559+
const PerlIOl *l = *f;
2560+
*f = l->next;
2561+
Safefree(l);
2562+
}
2563+
return 1;
2564+
}
2565+
25212566
int
25222567
PerlIOUnix_oflags(const char *mode)
25232568
{
@@ -2721,7 +2766,10 @@ SSize_t
27212766
PerlIOUnix_read(pTHX_ PerlIO *f, void *vbuf, Size_t count)
27222767
{
27232768
dVAR;
2724-
const int fd = PerlIOSelf(f, PerlIOUnix)->fd;
2769+
int fd;
2770+
if (PerlIO_lockcnt(f)) /* in use: abort ungracefully */
2771+
return -1;
2772+
fd = PerlIOSelf(f, PerlIOUnix)->fd;
27252773
#ifdef PERLIO_STD_SPECIAL
27262774
if (fd == 0)
27272775
return PERLIO_STD_IN(fd, vbuf, count);
@@ -2744,7 +2792,9 @@ PerlIOUnix_read(pTHX_ PerlIO *f, void *vbuf, Size_t count)
27442792
}
27452793
return len;
27462794
}
2747-
PERL_ASYNC_CHECK();
2795+
/* EINTR */
2796+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
2797+
return -1;
27482798
}
27492799
/*NOTREACHED*/
27502800
}
@@ -2753,7 +2803,10 @@ SSize_t
27532803
PerlIOUnix_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
27542804
{
27552805
dVAR;
2756-
const int fd = PerlIOSelf(f, PerlIOUnix)->fd;
2806+
int fd;
2807+
if (PerlIO_lockcnt(f)) /* in use: abort ungracefully */
2808+
return -1;
2809+
fd = PerlIOSelf(f, PerlIOUnix)->fd;
27572810
#ifdef PERLIO_STD_SPECIAL
27582811
if (fd == 1 || fd == 2)
27592812
return PERLIO_STD_OUT(fd, vbuf, count);
@@ -2768,7 +2821,9 @@ PerlIOUnix_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
27682821
}
27692822
return len;
27702823
}
2771-
PERL_ASYNC_CHECK();
2824+
/* EINTR */
2825+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
2826+
return -1;
27722827
}
27732828
/*NOTREACHED*/
27742829
}
@@ -2803,7 +2858,9 @@ PerlIOUnix_close(pTHX_ PerlIO *f)
28032858
code = -1;
28042859
break;
28052860
}
2806-
PERL_ASYNC_CHECK();
2861+
/* EINTR */
2862+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
2863+
return -1;
28072864
}
28082865
if (code == 0) {
28092866
PerlIOBase(f)->flags &= ~PERLIO_F_OPEN;
@@ -3276,8 +3333,11 @@ SSize_t
32763333
PerlIOStdio_read(pTHX_ PerlIO *f, void *vbuf, Size_t count)
32773334
{
32783335
dVAR;
3279-
FILE * const s = PerlIOSelf(f, PerlIOStdio)->stdio;
3336+
FILE * s;
32803337
SSize_t got = 0;
3338+
if (PerlIO_lockcnt(f)) /* in use: abort ungracefully */
3339+
return -1;
3340+
s = PerlIOSelf(f, PerlIOStdio)->stdio;
32813341
for (;;) {
32823342
if (count == 1) {
32833343
STDCHAR *buf = (STDCHAR *) vbuf;
@@ -3297,7 +3357,8 @@ PerlIOStdio_read(pTHX_ PerlIO *f, void *vbuf, Size_t count)
32973357
got = -1;
32983358
if (got >= 0 || errno != EINTR)
32993359
break;
3300-
PERL_ASYNC_CHECK();
3360+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
3361+
return -1;
33013362
SETERRNO(0,0); /* just in case */
33023363
}
33033364
return got;
@@ -3366,12 +3427,15 @@ PerlIOStdio_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
33663427
{
33673428
dVAR;
33683429
SSize_t got;
3430+
if (PerlIO_lockcnt(f)) /* in use: abort ungracefully */
3431+
return -1;
33693432
for (;;) {
33703433
got = PerlSIO_fwrite(vbuf, 1, count,
33713434
PerlIOSelf(f, PerlIOStdio)->stdio);
33723435
if (got >= 0 || errno != EINTR)
33733436
break;
3374-
PERL_ASYNC_CHECK();
3437+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
3438+
return -1;
33753439
SETERRNO(0,0); /* just in case */
33763440
}
33773441
return got;
@@ -3533,9 +3597,12 @@ PerlIOStdio_set_ptrcnt(pTHX_ PerlIO *f, STDCHAR * ptr, SSize_t cnt)
35333597
IV
35343598
PerlIOStdio_fill(pTHX_ PerlIO *f)
35353599
{
3536-
FILE * const stdio = PerlIOSelf(f, PerlIOStdio)->stdio;
3600+
FILE * stdio;
35373601
int c;
35383602
PERL_UNUSED_CONTEXT;
3603+
if (PerlIO_lockcnt(f)) /* in use: abort ungracefully */
3604+
return -1;
3605+
stdio = PerlIOSelf(f, PerlIOStdio)->stdio;
35393606

35403607
/*
35413608
* fflush()ing read-only streams can cause trouble on some stdio-s
@@ -3550,7 +3617,8 @@ PerlIOStdio_fill(pTHX_ PerlIO *f)
35503617
break;
35513618
if (! PerlSIO_ferror(stdio) || errno != EINTR)
35523619
return EOF;
3553-
PERL_ASYNC_CHECK();
3620+
if (PL_sig_pending && S_perlio_async_run(aTHX_ f))
3621+
return -1;
35543622
SETERRNO(0,0);
35553623
}
35563624

@@ -4082,7 +4150,8 @@ PerlIOBuf_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
40824150
PerlIO_flush(f);
40834151
}
40844152
if (b->ptr >= (b->buf + b->bufsiz))
4085-
PerlIO_flush(f);
4153+
if (PerlIO_flush(f) == -1)
4154+
return -1;
40864155
}
40874156
if (PerlIOBase(f)->flags & PERLIO_F_UNBUF)
40884157
PerlIO_flush(f);

perliol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ struct _PerlIO {
9090
#define PERLIO_F_FASTGETS 0x00400000
9191
#define PERLIO_F_TTY 0x00800000
9292
#define PERLIO_F_NOTREG 0x01000000
93+
#define PERLIO_F_CLEARED 0x02000000 /* layer cleared but not freed */
9394

9495
#define PerlIOBase(f) (*(f))
9596
#define PerlIOSelf(f,type) ((type *)PerlIOBase(f))

pod/perlipc.pod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ operation.)
408408
The default in Perl 5.7.3 and later is to automatically use
409409
the C<:perlio> layer.
410410

411+
Note that it is not advisable to access a file handle within a signal
412+
handler where that signal has interrupted an I/O operation on that same
413+
handle. While perl will at least try hard not to crash, there are no
414+
guarantees of data integrity; for example, some data might get dropped or
415+
written twice.
416+
411417
Some networking library functions like gethostbyname() are known to have
412418
their own implementations of timeouts which may conflict with your
413419
timeouts. If you have problems with such functions, try using the POSIX

t/io/eintr.t

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!./perl
2+
3+
# If a read or write is interrupted by a signal, Perl will call the
4+
# signal handler and then attempt to restart the call. If the handler does
5+
# something nasty like close the handle or pop layers, make sure that the
6+
# read/write handles this gracefully (for some definition of 'graceful':
7+
# principally, don't segfault).
8+
9+
BEGIN {
10+
chdir 't' if -d 't';
11+
@INC = '../lib';
12+
}
13+
14+
use warnings;
15+
use strict;
16+
use Config;
17+
18+
require './test.pl';
19+
20+
my $piped;
21+
eval {
22+
pipe my $in, my $out;
23+
$piped = 1;
24+
};
25+
if (!$piped) {
26+
skip_all('pipe not implemented');
27+
exit 0;
28+
}
29+
unless (exists $Config{'d_alarm'}) {
30+
skip_all('alarm not implemented');
31+
exit 0;
32+
}
33+
34+
# XXX for some reason the stdio layer doesn't seem to interrupt
35+
# write system call when the alarm triggers. This makes the tests
36+
# hang.
37+
38+
if (exists $ENV{PERLIO} && $ENV{PERLIO} =~ /stdio/ ) {
39+
skip_all('stdio not supported for this script');
40+
exit 0;
41+
}
42+
43+
my ($in, $out, $st, $sigst, $buf);
44+
45+
plan(tests => 10);
46+
47+
48+
# make two handles that will always block
49+
50+
sub fresh_io {
51+
undef $in; undef $out; # use fresh handles each time
52+
pipe $in, $out;
53+
$sigst = "";
54+
}
55+
56+
$SIG{PIPE} = 'IGNORE';
57+
58+
# close during read
59+
60+
fresh_io;
61+
$SIG{ALRM} = sub { $sigst = close($in) ? "ok" : "nok" };
62+
alarm(1);
63+
$st = read($in, $buf, 1);
64+
alarm(0);
65+
is($sigst, 'ok', 'read/close: sig handler close status');
66+
ok(!$st, 'read/close: read status');
67+
ok(!close($in), 'read/close: close status');
68+
69+
# die during read
70+
71+
fresh_io;
72+
$SIG{ALRM} = sub { die };
73+
alarm(1);
74+
$st = eval { read($in, $buf, 1) };
75+
alarm(0);
76+
ok(!$st, 'read/die: read status');
77+
ok(close($in), 'read/die: close status');
78+
79+
# close during print
80+
81+
fresh_io;
82+
$SIG{ALRM} = sub { $sigst = close($out) ? "ok" : "nok" };
83+
$buf = "a" x 1_000_000 . "\n"; # bigger than any pipe buffer hopefully
84+
select $out; $| = 1; select STDOUT;
85+
alarm(1);
86+
$st = print $out $buf;
87+
alarm(0);
88+
is($sigst, 'nok', 'print/close: sig handler close status');
89+
ok(!$st, 'print/close: print status');
90+
ok(!close($out), 'print/close: close status');
91+
92+
# die during print
93+
94+
fresh_io;
95+
$SIG{ALRM} = sub { die };
96+
$buf = "a" x 1_000_000 . "\n"; # bigger than any pipe buffer hopefully
97+
select $out; $| = 1; select STDOUT;
98+
alarm(1);
99+
$st = eval { print $out $buf };
100+
alarm(0);
101+
ok(!$st, 'print/die: print status');
102+
# the close will hang since there's data to flush, so use alarm
103+
alarm(1);
104+
ok(!eval {close($out)}, 'print/die: close status');
105+
alarm(0);
106+
107+
# close during close
108+
109+
# Apparently there's nothing in standard Linux that can cause an
110+
# EINTR in close(2); but run the code below just in case it does on some
111+
# platform, just to see if it segfaults.
112+
fresh_io;
113+
$SIG{ALRM} = sub { $sigst = close($in) ? "ok" : "nok" };
114+
alarm(1);
115+
close $in;
116+
alarm(0);
117+
118+
# die during close
119+
120+
fresh_io;
121+
$SIG{ALRM} = sub { die };
122+
alarm(1);
123+
eval { close $in };
124+
alarm(0);
125+
126+
# vim: ts=4 sts=4 sw=4:

0 commit comments

Comments
 (0)