summaryrefslogtreecommitdiff
path: root/pp_ctl.c
diff options
context:
space:
mode:
authorFather Chrysostomos <sprout@cpan.org>2018-01-07 22:23:03 -0800
committerFather Chrysostomos <sprout@cpan.org>2018-01-08 08:18:44 -0800
commit6d90e98384148a470db6f66439a13e5955418298 (patch)
treefafda3a7cc87c234040bce4664b09dd6e5277edd /pp_ctl.c
parent88490382efb37ca723204b6b6a540d37b76bdc19 (diff)
downloadperl-6d90e98384148a470db6f66439a13e5955418298.tar.gz
[perl #130936] Forbid some cases of inward goto
This commit in general forbids entry into the parameter of a binary or list operator, to avoid crashes and stack corruption. In cases like goto f; push @array, do { f: } and goto f; $a + do { f: }; it’s not possible to fix this in general. Cases like goto f; do { f: } + $a; (jumping into the first parameter) have never caused problems, but I went ahead and forbad that usage too, since it would be too compli- cated to figure out exactly which parameter is being jumped into. (It’s not impossible; it would just double the amount of code used to find labels.) List operators taking just a simple list, such as die(), have never worked properly, because goto() bypasses the pushmark. They could be made to work, but that would require extra work to distinguish cases like push and print that have a first operand (sometimes implicit for print) of a specific type. I figured it was easier just to forbid jumping into any list operator. It’s also much easier to document.
Diffstat (limited to 'pp_ctl.c')
-rw-r--r--pp_ctl.c37
1 files changed, 31 insertions, 6 deletions
diff --git a/pp_ctl.c b/pp_ctl.c
index e6d39f289e..6e5f34dbd5 100644
--- a/pp_ctl.c
+++ b/pp_ctl.c
@@ -2644,6 +2644,8 @@ PP(pp_redo)
return redo_op;
}
+#define UNENTERABLE (OP *)1
+
STATIC OP *
S_dofindlabel(pTHX_ OP *o, const char *label, STRLEN len, U32 flags, OP **opstack, OP **oplimit)
{
@@ -2662,9 +2664,22 @@ S_dofindlabel(pTHX_ OP *o, const char *label, STRLEN len, U32 flags, OP **opstac
o->op_type == OP_LEAVEGIVEN)
{
*ops++ = cUNOPo->op_first;
- if (ops >= oplimit)
- Perl_croak(aTHX_ "%s", too_deep);
}
+ else if (o->op_flags & OPf_KIDS
+ && cUNOPo->op_first->op_type == OP_PUSHMARK) {
+ *ops++ = UNENTERABLE;
+ }
+ else if (o->op_flags & OPf_KIDS && PL_opargs[o->op_type]
+ && OP_CLASS(o) != OA_LOGOP
+ && o->op_type != OP_LINESEQ
+ && o->op_type != OP_SREFGEN
+ && o->op_type != OP_RV2CV) {
+ OP * const kid = cUNOPo->op_first;
+ if (OP_GIMME(kid, 0) != G_SCALAR || OpHAS_SIBLING(kid))
+ *ops++ = UNENTERABLE;
+ }
+ if (ops >= oplimit)
+ Perl_croak(aTHX_ "%s", too_deep);
*ops = 0;
if (o->op_flags & OPf_KIDS) {
OP *kid;
@@ -2695,8 +2710,9 @@ S_dofindlabel(pTHX_ OP *o, const char *label, STRLEN len, U32 flags, OP **opstac
if (kid->op_type == OP_NEXTSTATE || kid->op_type == OP_DBSTATE) {
if (ops == opstack)
*ops++ = kid;
- else if (ops[-1]->op_type == OP_NEXTSTATE ||
- ops[-1]->op_type == OP_DBSTATE)
+ else if (ops[-1] != UNENTERABLE
+ && (ops[-1]->op_type == OP_NEXTSTATE ||
+ ops[-1]->op_type == OP_DBSTATE))
ops[-1] = kid;
else
*ops++ = kid;
@@ -2716,6 +2732,9 @@ S_check_op_type(pTHX_ OP * const o)
/* Eventually we may want to stack the needed arguments
* for each op. For now, we punt on the hard ones. */
/* XXX This comment seems to me like wishful thinking. --sprout */
+ if (o == UNENTERABLE)
+ Perl_croak(aTHX_
+ "Can't \"goto\" into a binary or list expression");
if (o->op_type == OP_ENTERITER)
Perl_croak(aTHX_
"Can't \"goto\" into the middle of a foreach loop");
@@ -3069,7 +3088,10 @@ PP(pp_goto)
}
if (*enterops && enterops[1]) {
- I32 i = enterops[1]->op_type == OP_ENTER && in_block ? 2 : 1;
+ I32 i = enterops[1] != UNENTERABLE
+ && enterops[1]->op_type == OP_ENTER && in_block
+ ? 2
+ : 1;
if (enterops[i])
deprecate("\"goto\" to jump into a construct");
}
@@ -3088,7 +3110,10 @@ PP(pp_goto)
if (*enterops && enterops[1]) {
OP * const oldop = PL_op;
- ix = enterops[1]->op_type == OP_ENTER && in_block ? 2 : 1;
+ ix = enterops[1] != UNENTERABLE
+ && enterops[1]->op_type == OP_ENTER && in_block
+ ? 2
+ : 1;
for (; enterops[ix]; ix++) {
PL_op = enterops[ix];
S_check_op_type(aTHX_ PL_op);