Skip to content

Commit 829184b

Browse files
committed
av.c - av_store() do the refcount dance around magic av's
The api for av_store() says it is the callers responsibility to call SvREFCNT_inc() on the stored item after the store is successful. However inside of av_store() we store the item in the C level array before we trigger magic. To a certain extent this is required because mg_set(av) needs to be able to see the newly stored item. But if the mg_set() or other magic associated with the av_store() operation fails, we would end up with a double free situation, as we will long jump up the stack above and out of the call to av_store(), freeing the mortal as we go (via Perl_croak()), but leaving the reference to the now freed pointer in the array. When the next SV is allocated the reference will be reused, and then we are in a double free scenario. I see comments in pp_aassign talking about defusing the temps stack for the parameters it is passing in, and things like this, which at first looked related. But that commentary doesn't seem that relevant to me, as this bug could happen any time a scalar owned by one data structure was copied into an array with set magic which could die. Eg, I can easily imagine XS code that expected code like this (assume it handles magic properly) to work: SV **svp = av_fetch(av1,0,1); if (av_store(av2,0,*svp)) SvREFCNT_inc(*svp); but if av2 has set magic and it dies the end result will be that both av1 and av2 contain a visible reference to *svp, but its refcount will be 1. So I think this is a bug regardless of what pp_aassign does. This fixes Perl#20675
1 parent ac0b959 commit 829184b

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

av.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,47 @@ Perl_av_store(pTHX_ AV *av, SSize_t key, SV *val)
373373
}
374374
else if (AvREAL(av))
375375
SvREFCNT_dec(ary[key]);
376+
377+
/* store the val into the AV before we call magic so that the magic can
378+
* "see" the new value. Especially set magic on the AV itself. */
376379
ary[key] = val;
380+
377381
if (SvSMAGICAL(av)) {
378382
const MAGIC *mg = SvMAGIC(av);
379383
bool set = TRUE;
384+
/* We have to increment the refcount on val before we call any magic,
385+
* as it is now stored in the AV (just before this block), we will
386+
* then call the magic handlers which might die/Perl_croak, and
387+
* longjmp up the stack to the most recent exception trap. Which means
388+
* the caller code that would be expected to handle the refcount
389+
* increment likely would never be executed, leading to a double free.
390+
* This can happen in a case like
391+
*
392+
* @ary = (1);
393+
*
394+
* or this:
395+
*
396+
* if (av_store(av,n,sv)) SvREFCNT_inc(sv);
397+
*
398+
* where @ary/av has set magic applied to it which can die. In the
399+
* first case the sv representing 1 would be mortalized, so when the
400+
* set magic threw an exception it would be freed as part of the
401+
* normal stack unwind. However this leaves the av structure still
402+
* holding a valid visible pointer to the now freed value. In practice
403+
* the next SV created will reuse the same reference, but without the
404+
* refcount to account for the previous ownership and we end up with
405+
* warnings about a totally different variable being double freed in
406+
* the form of "attempt to free unreferenced variable"
407+
* warnings/errors.
408+
*
409+
* https://github.com/Perl/perl5/issues/20675
410+
*
411+
* Arguably the API for av_store is broken in the face of magic. Instead
412+
* av_store should be responsible for the refcount increment, and only
413+
* not do it when specifically told to do so (eg, when storing an
414+
* otherwise unreferenced scalar into an AV).
415+
*/
416+
SvREFCNT_inc(val); /* see comment above */
380417
for (; mg; mg = mg->mg_moremagic) {
381418
if (!isUPPER(mg->mg_type)) continue;
382419
if (val) {
@@ -389,6 +426,10 @@ Perl_av_store(pTHX_ AV *av, SSize_t key, SV *val)
389426
}
390427
if (set)
391428
mg_set(MUTABLE_SV(av));
429+
/* And now we are done the magic, we have to decrement it back as the av_store() api
430+
* says the caller is responsible for the refcount increment, assuming
431+
* av_store returns true. */
432+
SvREFCNT_dec(val);
392433
}
393434
return &ary[key];
394435
}

ext/XS-APItest/APItest.xs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,19 @@ S_myset_set(pTHX_ SV* sv, MAGIC* mg)
125125
return 0;
126126
}
127127

128+
static int
129+
S_myset_set_dies(pTHX_ SV* sv, MAGIC* mg)
130+
{
131+
PERL_UNUSED_ARG(sv);
132+
PERL_UNUSED_ARG(mg);
133+
croak("in S_myset_set_dies");
134+
return 0;
135+
}
136+
137+
128138
static MGVTBL vtbl_foo, vtbl_bar;
129139
static MGVTBL vtbl_myset = { 0, S_myset_set, 0, 0, 0, 0, 0, 0 };
140+
static MGVTBL vtbl_myset_dies = { 0, S_myset_set_dies, 0, 0, 0, 0, 0, 0 };
130141

131142
static int
132143
S_mycopy_copy(pTHX_ SV *sv, MAGIC* mg, SV *nsv, const char *name, I32 namlen) {
@@ -4838,6 +4849,13 @@ test_get_vtbl()
48384849
# attach ext magic to the SV pointed to by rsv that only has set magic,
48394850
# where that magic's job is to increment thingy
48404851

4852+
void
4853+
sv_magic_myset_dies(SV *rsv, SV *thingy)
4854+
CODE:
4855+
sv_magicext(SvRV(rsv), NULL, PERL_MAGIC_ext, &vtbl_myset_dies,
4856+
(const char *)thingy, 0);
4857+
4858+
48414859
void
48424860
sv_magic_myset(SV *rsv, SV *thingy)
48434861
CODE:
@@ -4863,6 +4881,25 @@ sv_magic_mycopy_count(SV *rsv)
48634881
OUTPUT:
48644882
RETVAL
48654883

4884+
int
4885+
my_av_store(SV *rsv, IV i, SV *sv)
4886+
CODE:
4887+
if (av_store((AV*)SvRV(rsv), i, sv)) {
4888+
SvREFCNT_inc(sv);
4889+
RETVAL = 1;
4890+
} else {
4891+
RETVAL = 0;
4892+
}
4893+
OUTPUT:
4894+
RETVAL
4895+
4896+
STRLEN
4897+
sv_refcnt(SV *sv)
4898+
CODE:
4899+
RETVAL = SvREFCNT(sv);
4900+
OUTPUT:
4901+
RETVAL
4902+
48664903

48674904
MODULE = XS::APItest PACKAGE = XS::APItest
48684905

ext/XS-APItest/t/magic.t

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,54 @@ is $@, "", 'PERL_MAGIC_ext is permitted on read-only things';
7676
is($i, 0, "hash () with set magic");
7777
}
7878

79+
{
80+
# check if set magic triggered by av_store() via aassign results in
81+
# unreferenced scalars being freed. IOW, results in a double store
82+
# without a corresponding refcount bump. If things work properly this
83+
# should not warn. If there is an issue it will.
84+
my @warn;
85+
local $SIG{__WARN__}= sub { push @warn, $_[0] };
86+
{
87+
my (@a, $i);
88+
sv_magic_myset_dies(\@a, $i);
89+
eval {
90+
$i = 0;
91+
@a = (1);
92+
};
93+
}
94+
is(0+@warn, 0,
95+
"If AV set magic dies via aassign it should not warn about double free");
96+
@warn = ();
97+
{
98+
my (@a, $i, $j);
99+
sv_magic_myset_dies(\@a, $i);
100+
eval {
101+
$j = "blorp";
102+
my_av_store(\@a,0,$j);
103+
};
104+
my $base_refcount = 2; # not sure where these come from.
105+
if (\$a[0] == \$j) {
106+
# in this case we expect to have an extra 2 refcounts,
107+
# one from $a[0] and one from $j itself.
108+
is( sv_refcnt($j), $base_refcount + 2,
109+
"\$a[0] is \$j, so refcount(\$j) should be 4");
110+
} else {
111+
# Note this branch isn't exercised. Whether by design
112+
# or not. I leave it here because it is a possible valid
113+
# outcome. It is marked TODO so if we start going down
114+
# this path we do so knowingly.
115+
diag "av_store has changed behavior - please review this test";
116+
TODO:{
117+
local $TODO = "av_store bug stores even if it dies during magic";
118+
# in this case we expect to have only 1 extra refcount,
119+
# from $j itself.
120+
is( sv_refcnt($j), $base_refcount + 1,
121+
"\$a[0] is not \$j, so refcount(\$j) should be 3");
122+
}
123+
}
124+
}
125+
is(0+@warn, 0,
126+
"AV set magic that dies via av_store should not warn about double free");
127+
}
128+
79129
done_testing;

0 commit comments

Comments
 (0)