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

Kris Van Hees kris.van.hees at oracle.com
Wed Apr 13 19:37:54 UTC 2022


Reviewed-by: Kris Van Hees <kris.van.hees at oracle.com>

... except that there is a XXX in a comment that seems to imply there is
some work left to be done.  Has this been done or found not needed, or
should this be a TODO?

See:
	/* XXX do we need to do alloca tainting here? try with structs */

On Tue, Apr 12, 2022 at 11:59:00AM +0100, Nick Alcock wrote:
> 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