[DTrace-devel] [PATCH v4 01/10] alloca: track alloca()ed allocations

Nick Alcock nick.alcock at oracle.com
Tue Apr 12 10:59:00 UTC 2022


This introduces a DT_IDFLG_ALLOCA flag which, when set, will (in future
commits) cause integer variable setting to subtract the address of the
scratch base from the assignee before assignment, variable fetching to
add it, and dereferences to bounds-check it. There is a matching parse
tree node flag DT_NF_ALLOCA which indicates that a parse tree node has
been influenced by an alloca'ed value.  There is also a
DT_IDFLG_NONALLOCA flag which indicates that an assignment happened that
did *not* involve an alloca()ed value: this will be used to prevent
reuse of the same variable for both alloca() and non-alloca() purposes.
The node flag propagates across most operations (but not dereferences,
because dereferencing a pointer yields a value, which should not have
its offset adjusted).  One function's identifier has this flag set by
default: obviously enough, alloca().  (Other functions that always
return alloca()ed pointers, or pointers into alloca()ed space, should do
the same.)

Propagating this is fairly amusing, because not only do we have to
handle propagation of these flags both to and from parse tree nodes and
identifiers (because you can do things like "foo = alloca(10); bar =
foo;") and across casts, but we have to handle cases like identifiers
with alloca'ed vars in their arglists (which will become relevant when
indexed associative arrays appear), ternaries in which one side is
allocaed and the other side isn't (which flips on a similarly-propagated
DT_NF_NONASSIGN flag to stop you using such things in assignments), and
the same possibility of changes sweeping backwards and forwards between
the predicate and the clause that already has to be handled for ++,
which can require the two to be cooked in either order and to be cooked
repeatedly (yes, you can alloca in a predicate, and you can use
alloca'ed memory in a predicate).

A few things are newly banned that were allowed before: in particular,
you can't say bar : -alloca(10). It makes little sense to negate a
pointer anyway, but this pointer's eventual representation as a
bounds-checked offset from the scratchmem array makes it even less
sensible.  You also cannot add or subtract pointers where one is
allocaed and the other is not, nor (as noted above) assign the results
of a partly-allocaed ternary to a variable.

The parser dn_flags is boosted to a ushort because we have run out of
flags :(

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
 libdtrace/dt_errtags.h |   2 +
 libdtrace/dt_ident.c   |  18 +++-
 libdtrace/dt_ident.h   |   2 +
 libdtrace/dt_open.c    |   4 +-
 libdtrace/dt_parser.c  | 216 ++++++++++++++++++++++++++++++++++++++++-
 libdtrace/dt_parser.h  |   5 +-
 libdtrace/dt_pcb.h     |   1 +
 7 files changed, 242 insertions(+), 6 deletions(-)

diff --git a/libdtrace/dt_errtags.h b/libdtrace/dt_errtags.h
index 83257a148d0f..d89dacc0d096 100644
--- a/libdtrace/dt_errtags.h
+++ b/libdtrace/dt_errtags.h
@@ -174,6 +174,8 @@ typedef enum {
 	D_USTACK_FRAMES,		/* ustack() frames arg bad type */
 	D_USTACK_STRSIZE,		/* ustack() strsize arg bad type */
 	D_USTACK_PROTO,			/* ustack() prototype mismatch */
+	D_ALLOCA_SIZE,			/* allocation too large */
+	D_ALLOCA_INCOMPAT,		/* pointer reused for alloca and non-alloca */
 	D_LQUANT_BASETYPE,		/* lquantize() bad base type */
 	D_LQUANT_BASEVAL,		/* lquantize() bad base value */
 	D_LQUANT_LIMTYPE,		/* lquantize() bad limit type */
diff --git a/libdtrace/dt_ident.c b/libdtrace/dt_ident.c
index 098f1fcbaaed..e34f8e8bd8df 100644
--- a/libdtrace/dt_ident.c
+++ b/libdtrace/dt_ident.c
@@ -979,18 +979,34 @@ dt_ident_cook(dt_node_t *dnp, dt_ident_t *idp, dt_node_t **pargp)
 	dtrace_attribute_t attr;
 	dt_node_t *args, *argp;
 	int argc = 0;
+	int alloca_args = 0;
 
 	attr = dt_node_list_cook(pargp, DT_IDFLG_REF);
 	args = pargp ? *pargp : NULL;
 
-	for (argp = args; argp != NULL; argp = argp->dn_list)
+	for (argp = args; argp != NULL; argp = argp->dn_list) {
 		argc++;
+		if (argp->dn_flags & DT_NF_ALLOCA)
+			alloca_args = 1;
+	}
 
 	idp->di_ops->di_cook(dnp, idp, argc, args);
 
 	if (idp->di_flags & DT_IDFLG_USER)
 		dnp->dn_flags |= DT_NF_USERLAND;
 
+	/*
+	 * Propagate alloca flags in both directions.  No need to propagate it
+	 * down into the arglist, but if the alloca flag is on in the arglist it
+	 * should also be on in both the identifer and the node.  (If this is
+	 * a node of the sort we propagate alloca taint into at all.)
+	 */
+
+	if (idp->di_flags & DT_IDFLG_ALLOCA ||
+	    dnp->dn_flags & DT_NF_ALLOCA ||
+	    alloca_args)
+		dt_cook_taint_alloca(dnp, idp, NULL);
+
 	return dt_attr_min(attr, idp->di_attr);
 }
 
diff --git a/libdtrace/dt_ident.h b/libdtrace/dt_ident.h
index 655f1116e525..56dfec0cda4d 100644
--- a/libdtrace/dt_ident.h
+++ b/libdtrace/dt_ident.h
@@ -94,6 +94,8 @@ typedef struct dt_ident {
 #define	DT_IDFLG_DECL	0x0400	/* variable is associated with explicit decl */
 #define	DT_IDFLG_ORPHAN	0x0800	/* variable is in a dt_node and not dt_idhash */
 #define	DT_IDFLG_BPF	0x1000	/* variable is BPF */
+#define	DT_IDFLG_ALLOCA	0x2000	/* variable holds an alloca()ed pointer */
+#define	DT_IDFLG_NONALLOCA 0x4000 /* variable known not to hold an alloca()ed pointer */
 
 #define DT_IDENT_UNDEF	UINT_MAX /* id for (as yet) undefined identifiers */
 
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index 5bdc6037beec..f11d54d8d55a 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -85,8 +85,8 @@ static const dt_provimpl_t *dt_providers[] = {
  * argument.
  */
 static const dt_ident_t _dtrace_globals[] = {
-{ "alloca", DT_IDENT_FUNC, 0, DIF_SUBR_ALLOCA, DT_ATTR_STABCMN, DT_VERS_1_0,
-	&dt_idops_func, "void *(size_t)" },
+{ "alloca", DT_IDENT_FUNC, DT_IDFLG_ALLOCA, DIF_SUBR_ALLOCA, DT_ATTR_STABCMN,
+	DT_VERS_1_0, &dt_idops_func, "void *(size_t)" },
 { "arg0", DT_IDENT_SCALAR, 0, DIF_VAR_ARG0, DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_type, "int64_t" },
 { "arg1", DT_IDENT_SCALAR, 0, DIF_VAR_ARG1, DT_ATTR_STABCMN, DT_VERS_1_0,
diff --git a/libdtrace/dt_parser.c b/libdtrace/dt_parser.c
index df2153375780..b8f37d8d6b17 100644
--- a/libdtrace/dt_parser.c
+++ b/libdtrace/dt_parser.c
@@ -643,6 +643,12 @@ dt_node_type_assign(dt_node_t *dnp, ctf_file_t *fp, ctf_id_t type)
 	uint_t kind = ctf_type_kind(fp, base);
 	ctf_encoding_t e;
 
+	/*
+	 * We do not blank out DT_NF_NONASSIGN or DT_NF_ALLOCA because
+	 * there is no way for repeated reevaluation of the same node to
+	 * cause a previously-alloc'ed or nonassigned node to become
+	 * non-allocated or non-nonassigned.
+	 */
 	dnp->dn_flags &=
 	    ~(DT_NF_SIGNED | DT_NF_REF | DT_NF_BITFIELD | DT_NF_USERLAND);
 
@@ -678,6 +684,23 @@ void
 dt_node_type_propagate(const dt_node_t *src, dt_node_t *dst)
 {
 	assert(src->dn_flags & DT_NF_COOKED);
+
+	if ((src->dn_flags & DT_NF_ALLOCA) && !(dst->dn_flags & DT_NF_ALLOCA))
+		yypcb->pcb_alloca_taints++;
+
+	/*
+	 * A previously-nonassignable node may become assignable if allocaness
+	 * later propagates to it.  Once this happens, it cannot become
+	 * nonassignable again (becaue allocaness cannot be turned off once
+	 * enabled).  This bumps the alloca taint counter, because it is quite
+	 * possible that both src and dst have allocaness at this point (in
+	 * fact, dst must already have it), but we still want to force
+	 * rescanning in order to propagate nonassignment further.
+	 */
+	if (!(src->dn_flags & DT_NF_NONASSIGN) &&
+	    (dst->dn_flags & DT_NF_NONASSIGN))
+		yypcb->pcb_alloca_taints++;
+
 	dst->dn_flags = src->dn_flags & ~DT_NF_LVALUE;
 	dst->dn_ctfp = src->dn_ctfp;
 	dst->dn_type = src->dn_type;
@@ -714,6 +737,29 @@ dt_node_type_size(const dt_node_t *dnp)
 	return ctf_type_size(dnp->dn_ctfp, dnp->dn_type);
 }
 
+void
+dt_node_prop_alloca(dt_node_t *dst, const dt_node_t *lp, const dt_node_t *rp)
+{
+	if (dst->dn_flags & DT_NF_ALLOCA)
+		return;
+
+	if (lp->dn_flags & DT_NF_ALLOCA) {
+		dt_cook_taint_alloca(dst, NULL, NULL);
+		if (lp->dn_flags & DT_NF_NONASSIGN) {
+			dst->dn_flags |= DT_NF_NONASSIGN;
+			yypcb->pcb_alloca_taints++;
+		}
+	}
+
+	if (rp && rp->dn_flags & DT_NF_ALLOCA) {
+		dt_cook_taint_alloca(dst, NULL, NULL);
+		if (rp->dn_flags & DT_NF_NONASSIGN) {
+			dst->dn_flags |= DT_NF_NONASSIGN;
+			yypcb->pcb_alloca_taints++;
+		}
+	}
+}
+
 /*
  * Determine if the specified parse tree node references an identifier of the
  * specified kind, and if so return a pointer to it; otherwise return NULL.
@@ -1826,6 +1872,10 @@ dt_node_op1(int op, dt_node_t *cp)
 				cp->dn_value &= ~0ULL >>
 				    (64 - dt_node_type_size(cp) * NBBY);
 			}
+			if (cp->dn_flags & DT_NF_ALLOCA) {
+				xyerror(D_UNKNOWN,
+					"cannot negate alloca()ed pointers");
+			}
 			/*FALLTHRU*/
 		case DT_TOK_IPOS:
 			return cp;
@@ -2593,6 +2643,42 @@ dt_node_tstring(dt_node_t *fnp, uintmax_t val)
 	return dnp;
 }
 
+/*
+ * Flip on the alloca flag for a node and/or identifier, if not already set, and
+ * bump the alloca taint counter in the pcb.  The SRC, if present, is used as
+ * a propagation source for the nonassignment flag.
+ *
+ * Only a limited variety of identifiers are tainted: roughly, those that can
+ * plausibly store alloca pointers.  Parse tree nodes corresponding to
+ * nontainted identifers are not tainted either.
+ */
+void
+dt_cook_taint_alloca(dt_node_t *dnp, dt_ident_t *idp, dt_node_t *src)
+{
+	if (idp && !(idp->di_flags & DT_IDFLG_ALLOCA)) {
+		if (idp->di_kind == DT_IDENT_ARRAY ||
+		    idp->di_kind == DT_IDENT_SCALAR ||
+		    idp->di_kind == DT_IDENT_AGG ||
+		    idp->di_kind == DT_IDENT_XLSOU ||
+		    idp->di_kind == DT_IDENT_XLPTR) {
+			idp->di_flags |= DT_IDFLG_ALLOCA;
+			yypcb->pcb_alloca_taints++;
+		} else
+			/*
+			 * Not the sort of thing we should taint.
+			 */
+			return;
+	}
+
+	if (dnp && !(dnp->dn_flags & DT_NF_ALLOCA)) {
+		dnp->dn_flags |= DT_NF_ALLOCA;
+		yypcb->pcb_alloca_taints++;
+		if (src && (src->dn_flags & DT_NF_NONASSIGN)) {
+			dnp->dn_flags |= DT_NF_NONASSIGN;
+		}
+	}
+}
+
 /*
  * This function provides the underlying implementation of cooking an
  * identifier given its node, a hash of dynamic identifiers, an identifier
@@ -2698,6 +2784,10 @@ dt_xcook_ident(dt_node_t *dnp, dt_idhash_t *dhp, uint_t idkind, int create)
 		if (idp->di_flags & DT_IDFLG_WRITE)
 			dnp->dn_flags |= DT_NF_WRITABLE;
 
+		if ((idp->di_flags & DT_IDFLG_ALLOCA) ||
+		    (dnp->dn_flags & DT_NF_ALLOCA))
+			dt_cook_taint_alloca(dnp, idp, NULL);
+
 		dt_node_attr_assign(dnp, attr);
 
 	} else if (dhp == dtp->dt_globals && scope != DTRACE_OBJ_EXEC &&
@@ -2817,6 +2907,16 @@ dt_xcook_ident(dt_node_t *dnp, dt_idhash_t *dhp, uint_t idkind, int create)
 		dnp->dn_ident = idp;
 		dnp->dn_flags |= DT_NF_LVALUE | DT_NF_WRITABLE;
 
+		/*
+		 * Still a good idea, but not relevant for assignments: see
+		 * dt_cook_ident:DT_TOK_ASGN for more.  In particular, this is
+		 * not a kind of assignment, so we should not turn on NONALLOCA
+		 * here.
+		 */
+		if ((idp->di_flags & DT_IDFLG_ALLOCA) ||
+		    (dnp->dn_flags & DT_NF_ALLOCA))
+			dt_cook_taint_alloca(dnp, idp, NULL);
+
 		dt_node_attr_assign(dnp, attr);
 
 	} else if (scope != DTRACE_OBJ_EXEC) {
@@ -2987,6 +3087,10 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 			dnp->dn_flags |= DT_NF_USERLAND;
 		break;
 
+		/* Dereferenced pointers cannot be alloca pointers any more.  */
+		dnp->dn_flags &= ~(DT_NF_ALLOCA | DT_NF_NONASSIGN);
+		break;
+
 	case DT_TOK_IPOS:
 	case DT_TOK_INEG:
 		if (!dt_node_is_arith(cp)) {
@@ -3046,6 +3150,9 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 
 		if (cp->dn_flags & DT_NF_USERLAND)
 			dnp->dn_flags |= DT_NF_USERLAND;
+
+		if (cp->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(dnp, NULL, cp);
 		break;
 
 	case DT_TOK_SIZEOF:
@@ -3071,6 +3178,8 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 			    dt_node_type_name(cp, n, sizeof(n)));
 		}
 		dt_node_type_assign(dnp, DT_STR_CTFP(dtp), DT_STR_TYPE(dtp));
+		if (cp->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(dnp, NULL, cp);
 		break;
 
 	case DT_TOK_PREINC:
@@ -3098,6 +3207,9 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 		}
 
 		dt_node_type_propagate(cp, dnp); /* see K&R[A7.4.1] */
+
+		if (cp->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(dnp, NULL, cp);
 		break;
 
 	default:
@@ -3186,6 +3298,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 		}
 
 		dt_node_promote(lp, rp, dnp); /* see K&R[A7.11-13] */
+		dt_node_prop_alloca(dnp, lp, rp);
 		break;
 
 	case DT_TOK_LSH:
@@ -3212,6 +3325,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 		}
 
 		dt_node_promote(lp, rp, dnp); /* see K&R[A7.6] */
+		dt_node_prop_alloca(dnp, lp, rp);
 		break;
 
 	case DT_TOK_MUL:
@@ -3225,6 +3339,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 		}
 
 		dt_node_promote(lp, rp, dnp); /* see K&R[A7.6] */
+		dt_node_prop_alloca(dnp, lp, rp);
 		break;
 
 	case DT_TOK_LAND:
@@ -3240,6 +3355,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 
 		dt_node_type_assign(dnp, DT_INT_CTFP(dtp), DT_INT_TYPE(dtp));
 		dt_node_attr_assign(dnp, dt_attr_min(lp->dn_attr, rp->dn_attr));
+		dt_node_prop_alloca(dnp, lp, rp);
 		break;
 
 	case DT_TOK_LT:
@@ -3365,6 +3481,21 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 			    dt_node_type_name(lp, n1, sizeof(n1)), opstr(op),
 			    dt_node_type_name(rp, n2, sizeof(n2)));
 
+		/*
+		 * alloca()ed pointers and non-alloca()ed pointers must be to
+		 * distinct objects, and the address of the alloca()ed range is
+		 * an implementation detail, so pre-emptively block attempts to
+		 * add or subtract alloca()ed pointers and non-alloca()ed
+		 * pointers to/from each other.
+		 */
+		if (lp_is_ptr && rp_is_ptr && ((lp->dn_flags & DT_NF_ALLOCA) !=
+					       (rp->dn_flags & DT_NF_ALLOCA)))
+			xyerror(D_OP_INCOMPAT, "adding or subtracting pointers "
+				"to alloca and non-alloca space is meaningless: "
+				"\"%s\" %s \"%s\"\n",
+				dt_node_type_name(lp, n1, sizeof(n1)), opstr(op),
+				dt_node_type_name(rp, n2, sizeof(n2)));
+
 		/*
 		 * Array bounds-checking.  (Non-associative arrays only.)
 		 */
@@ -3388,6 +3519,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 
 		dt_node_type_assign(dnp, ctfp, type);
 		dt_node_attr_assign(dnp, dt_attr_min(lp->dn_attr, rp->dn_attr));
+		dt_node_prop_alloca(dnp, lp, rp);
 
 		if (uref)
 			dnp->dn_flags |= DT_NF_USERLAND;
@@ -3634,6 +3766,23 @@ asgn_common:
 			    "to a writable variable\n", opstr(op));
 		}
 
+		if ((lp->dn_kind == DT_NODE_VAR) &&
+		    (rp->dn_flags & DT_NF_NONASSIGN)) {
+			xyerror(D_ALLOCA_INCOMPAT, "ternary conditional with "
+			    "alloca and non-alloca branches cannot be "
+			    "assigned to a variable\n");
+		}
+
+		dt_ident_t *lp_idp = NULL;
+
+		if (lp->dn_kind == DT_NODE_VAR)
+			lp_idp = lp->dn_ident;
+
+		if (rp->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(lp, lp_idp, rp);
+		else if (lp_idp)
+			lp_idp->di_flags |= DT_IDFLG_NONALLOCA;
+
 		dt_node_type_propagate(lp, dnp); /* see K&R[A7.17] */
 		dt_node_attr_assign(dnp, dt_attr_min(lp->dn_attr, rp->dn_attr));
 		break;
@@ -3778,6 +3927,8 @@ asgn_common:
 		if (lp->dn_flags & DT_NF_WRITABLE)
 			dnp->dn_flags |= DT_NF_WRITABLE;
 
+		/* XXX do we need to do alloca tainting here? try with structs */
+
 		if (uref && (kind == CTF_K_POINTER ||
 		    (dnp->dn_flags & DT_NF_REF)))
 			dnp->dn_flags |= DT_NF_USERLAND;
@@ -3871,6 +4022,11 @@ asgn_common:
 		dnp->dn_args = rp;
 		dnp->dn_list = NULL;
 
+		if (dnp->dn_args->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(dnp, idp, dnp->dn_args);
+		else
+			idp->di_flags |= DT_IDFLG_NONALLOCA;
+
 		dt_node_free(lp);
 		return dt_node_cook(dnp, idflags);
 	}
@@ -3890,6 +4046,7 @@ asgn_common:
 		}
 
 		dnp->dn_ident = dt_xlator_ident(dxp, lp->dn_ctfp, lp->dn_type);
+		dt_node_prop_alloca(dnp, lp, rp);
 		dt_node_type_assign(dnp, DT_DYN_CTFP(dtp), DT_DYN_TYPE(dtp));
 		dt_node_attr_assign(dnp,
 		    dt_attr_min(rp->dn_attr, dnp->dn_ident->di_attr));
@@ -3936,6 +4093,17 @@ asgn_common:
 			    dt_node_type_name(lp, n2, sizeof(n2)));
 		}
 
+		/*
+		 * You cannot cast away allocaness.  (You also can't cast it
+		 * into existence where it was not before, but since there is no
+		 * syntactic way to specify allocaness, we don't need to cover
+		 * that case.  This maintains the invariant that alloca flags
+		 * can only ever transition from off to on, preventing the
+		 * dt_cook_clause loop from inflooping.)
+		 */
+		if (rp->dn_flags & DT_NF_ALLOCA)
+			dt_cook_taint_alloca(lp, NULL, rp);
+
 		dt_node_type_propagate(lp, dnp); /* see K&R[A7.5] */
 		dt_node_attr_assign(dnp, dt_attr_min(lp->dn_attr, rp->dn_attr));
 		break;
@@ -3957,6 +4125,7 @@ asgn_common:
 
 		dt_node_type_propagate(rp, dnp); /* see K&R[A7.18] */
 		dt_node_attr_assign(dnp, dt_attr_min(lp->dn_attr, rp->dn_attr));
+		dt_node_prop_alloca(dnp, rp, NULL);
 		break;
 
 	default:
@@ -3972,6 +4141,7 @@ asgn_common:
 	 */
 	if (dnp->dn_op == DT_TOK_LBRAC && op == DT_TOK_ADD) {
 		dt_node_t *pnp;
+		dt_node_t *ret;
 
 		if (rp->dn_list != NULL) {
 			xyerror(D_ARR_BADREF,
@@ -3994,7 +4164,12 @@ asgn_common:
 		pnp->dn_link = dnp->dn_link;
 		dnp->dn_link = pnp;
 
-		return dt_node_cook(pnp, DT_IDFLG_REF);
+		ret = dt_node_cook(pnp, DT_IDFLG_REF);
+
+		/*
+		 * This is a dereference: do not propagate alloca taint.
+		 */
+		return ret;
 	}
 
 	return dnp;
@@ -4047,10 +4222,27 @@ dt_cook_op3(dt_node_t *dnp, uint_t idflags)
 		    "used in a conditional context\n");
 	}
 
+	/*
+	 * An extra condition not expressed in the type system: if one side is
+	 * an alloca-derived pointer, and the other side is not, the resulting
+	 * value cannot be assigned to a global.  This flag is propagated
+	 * just like DT_NF_ALLOCA, except that (obviously) it cannot
+	 * propagate into an identifier.
+	 */
+	if ((lp->dn_flags & DT_NF_ALLOCA) != (rp->dn_flags & DT_NF_ALLOCA))
+		dnp->dn_flags |= DT_NF_NONASSIGN;
+
+	if ((lp->dn_flags & DT_NF_NONASSIGN) ||
+	    (rp->dn_flags & DT_NF_NONASSIGN))
+		dnp->dn_flags |= DT_NF_NONASSIGN;
+
 	dt_node_type_assign(dnp, ctfp, type);
 	dt_node_attr_assign(dnp, dt_attr_min(dnp->dn_expr->dn_attr,
 	    dt_attr_min(lp->dn_attr, rp->dn_attr)));
 
+	if (lp->dn_flags & DT_NF_ALLOCA)
+		dt_cook_taint_alloca(dnp, NULL, NULL);
+
 	return dnp;
 }
 
@@ -4129,12 +4321,21 @@ dt_cook_aggregation(dt_node_t *dnp, uint_t idflags)
  *
  * but it doesn't seem worth the complexity to handle such rare cases.  The
  * user can simply use the D variable declaration syntax to work around them.
+ *
+ * In addition, we count the total number of cases where we needed to set the
+ * relevant alloca flag, or set or reset the nonassign flag, on an identifier or
+ * node, and as long as it keeps rising, we reinvoke.  (This will always
+ * terminate, and soon, so there is no danger of inflooping from this
+ * either. Proof: the alloca flag can only be enabled, never disabled, and the
+ * nonassign flag is only ever caused to flip in either direction by an earlier
+ * change to at least one alloca flag).
  */
 static dt_node_t *
 dt_cook_clause(dt_node_t *dnp, uint_t idflags)
 {
 	volatile int err, tries;
 	jmp_buf ojb;
+	int last_alloca_taints;
 
 	/*
 	 * Before assigning dn_ctxattr, temporarily assign the probe attribute
@@ -4154,6 +4355,9 @@ dt_cook_clause(dt_node_t *dnp, uint_t idflags)
 			longjmp(yypcb->pcb_jmpbuf, err);
 	}
 
+taint_retry:
+	last_alloca_taints = yypcb->pcb_alloca_taints;
+
 	if (tries == 0) {
 		yylabel("action list");
 
@@ -4188,6 +4392,8 @@ dt_cook_clause(dt_node_t *dnp, uint_t idflags)
 		yylabel(NULL);
 	}
 
+	if (yypcb->pcb_alloca_taints > last_alloca_taints)
+		goto taint_retry;
 	return dnp;
 }
 
@@ -4635,6 +4841,10 @@ dt_node_printr(dt_node_t *dnp, FILE *fp, int depth)
 			strcat(n, ",BITF");
 		if (dnp->dn_flags & DT_NF_USERLAND)
 			strcat(n, ",USER");
+		if (dnp->dn_flags & DT_NF_ALLOCA)
+			strcat(n, ",ALLOCA");
+		if (dnp->dn_flags & DT_NF_NONASSIGN)
+			strcat(n, ",NONASSIGN");
 		strcat(buf, n + 1);
 	} else
 		strcat(buf, "0");
@@ -4660,7 +4870,9 @@ dt_node_printr(dt_node_t *dnp, FILE *fp, int depth)
 		break;
 
 	case DT_NODE_VAR:
-		fprintf(fp, "VARIABLE %s%s (%s)\n",
+		fprintf(fp, "VARIABLE %s%s%s (%s)\n",
+		    (dnp->dn_ident->di_flags & DT_IDFLG_ALLOCA) ? "(alloca-assigned) " :
+		    (dnp->dn_ident->di_flags & DT_IDFLG_NONALLOCA) ? "(normally-assigned) " : "",
 		    (dnp->dn_ident->di_flags & DT_IDFLG_LOCAL) ? "this->" :
 		    (dnp->dn_ident->di_flags & DT_IDFLG_TLS) ? "self->" : "",
 		    dnp->dn_ident->di_name, buf);
diff --git a/libdtrace/dt_parser.h b/libdtrace/dt_parser.h
index cd2820ebe43f..c09c25706c2d 100644
--- a/libdtrace/dt_parser.h
+++ b/libdtrace/dt_parser.h
@@ -31,7 +31,7 @@ typedef struct dt_node {
 	ctf_file_t *dn_ctfp;	/* CTF type container for node's type */
 	ctf_id_t dn_type;	/* CTF type reference for node's type */
 	uchar_t dn_kind;	/* node kind (DT_NODE_*, defined below) */
-	uchar_t dn_flags;	/* node flags (DT_NF_*, defined below) */
+	ushort_t dn_flags;	/* node flags (DT_NF_*, defined below) */
 	ushort_t dn_op;		/* operator (DT_TOK_*, defined by lex) */
 	int dn_line;		/* line number for error messages */
 	int dn_reg;		/* register allocated by cg */
@@ -158,6 +158,8 @@ typedef struct dt_node {
 #define	DT_NF_WRITABLE	0x10	/* node is writable (can be modified) */
 #define	DT_NF_BITFIELD	0x20	/* node is an integer bitfield */
 #define	DT_NF_USERLAND	0x40	/* data is a userland address */
+#define	DT_NF_ALLOCA	0x80	/* pointer derived from alloca() */
+#define	DT_NF_NONASSIGN	0x100	/* node is not assignable */
 
 #define	DT_TYPE_NAMELEN	128	/* reasonable size for ctf_type_name() */
 
@@ -206,6 +208,7 @@ extern dt_node_t *dt_node_tstring(dt_node_t *, uintmax_t);
 
 extern dt_node_t *dt_node_link(dt_node_t *, dt_node_t *);
 extern dt_node_t *dt_node_cook(dt_node_t *, uint_t);
+extern void dt_cook_taint_alloca(dt_node_t *, dt_ident_t *, dt_node_t *);
 
 extern dt_node_t *dt_node_xalloc(dtrace_hdl_t *, int);
 extern void dt_node_free(dt_node_t *);
diff --git a/libdtrace/dt_pcb.h b/libdtrace/dt_pcb.h
index cc39a0f3d88e..4e3a981c6a48 100644
--- a/libdtrace/dt_pcb.h
+++ b/libdtrace/dt_pcb.h
@@ -73,6 +73,7 @@ typedef struct dt_pcb {
 	int pcb_sou_deref;	/* lexer in struct/union dereference */
 	int pcb_xlator_input;	/* in translator input type */
 	int pcb_array_dimens;	/* in array dimensions */
+	int pcb_alloca_taints;	/* number of alloca taint changes */
 } dt_pcb_t;
 
 extern void dt_pcb_push(dtrace_hdl_t *, dt_pcb_t *);
-- 
2.35.1




More information about the DTrace-devel mailing list