[DTrace-devel] [PATCH v5 04/10] alloca: add alloca() itself

Nick Alcock nick.alcock at oracle.com
Thu Apr 14 13:26:00 UTC 2022


We add a new scratchsize option (default value an arbitrary 256), and a
new scratchmem dctx pointer.  We allocate twice as much space as we
asked for (only half of which is usable), because the verifier can only
use the values of variables to adjust its bounds in very limited
conditions: so we leave enough space to write the full size of scratch
space to the last byte of scratch space without overflow.  (Runtime
checks will prohibit such things, but the verifier doesn't know about
those.)

The top of currently-allocated scratch space is indicated via a single
new 8-byte-aligned value in the machine state, DMST_SCRATCH_TOP.  It is
reset to 0 before each clause is invoked.

alloca itself is fairly pedestrian given all that.  The size check is
fairly trivial: the only fiddly bit is the alignment (thanks to Kris Van
Hees for that).

Overly large allocations are unconditionally refused via a compile-time
error, before even generating any code, preventing the verifier from
concluding that too-large allocations can't succeed and therefore
the success branch in the alloca size check is unreachable code.

alloca'ed pointers are not yet terribly usable at this stage: you can't
dereference them because they are not map_values, just arbitrary
scalars.  Thus there are no tests yet either.

(One new test fails temporarily:
test/unittest/codegen/tst.stack_layout.sh.  It needs adjusting for the
new dmst variables, but we still have more to add in later commits.)

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 include/dtrace/options_defines.h |   3 +-
 libdtrace/dt_bpf.c               |  13 ++++
 libdtrace/dt_cg.c                | 100 ++++++++++++++++++++++++++++++-
 libdtrace/dt_dctx.h              |  22 ++++---
 libdtrace/dt_dlibs.c             |   1 +
 libdtrace/dt_open.c              |   6 ++
 libdtrace/dt_options.c           |  16 +++++
 7 files changed, 150 insertions(+), 11 deletions(-)

diff --git a/include/dtrace/options_defines.h b/include/dtrace/options_defines.h
index 5385f2f2dcde..337cd9d52e56 100644
--- a/include/dtrace/options_defines.h
+++ b/include/dtrace/options_defines.h
@@ -60,7 +60,8 @@
 #define	DTRACEOPT_BPFLOGSIZE	30	/* BPF verifier log, max # bytes */
 #define	DTRACEOPT_MAXFRAMES	31	/* maximum number of stack frames */
 #define	DTRACEOPT_BPFLOG	32	/* always output BPF verifier log */
-#define	DTRACEOPT_MAX		33	/* number of options */
+#define	DTRACEOPT_SCRATCHSIZE	33	/* max scratch size permitted */
+#define	DTRACEOPT_MAX		34	/* number of options */
 
 #define	DTRACEOPT_UNSET		(dtrace_optval_t)-2	/* unset option */
 
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index ade09b1528a1..7a96647412c8 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -204,6 +204,8 @@ populate_probes_map(dtrace_hdl_t *dtp, int fd)
  *		used to store the DTrace machine state, the temporary output
  *		buffer, and temporary storage for stack traces, string
  *		manipulation, etc.
+ * - scratchmem: Storage for alloca() and other per-clause scratch space,
+ *               implemented just as for mem.
  * - strtab:	String table map.  This is a global map with a singleton
  *		element (key 0) that contains the entire string table as a
  *		concatenation of all unique strings (each terminated with a
@@ -238,6 +240,7 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 	int		ci_mapfd, st_mapfd, pr_mapfd;
 	uint64_t	key = 0;
 	size_t		strsize = dtp->dt_options[DTRACEOPT_STRSIZE];
+	size_t		scratchsize = dtp->dt_options[DTRACEOPT_SCRATCHSIZE];
 	uint8_t		*buf, *end;
 	char		*strtab;
 
@@ -310,6 +313,16 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 			sizeof(uint32_t), memsz, 1) == -1)
 		return -1;		/* dt_errno is set for us */
 
+	/*
+	 * The size for this is twice what it needs to be, to allow us to bcopy
+	 * things the size of the scratch space to the start of the scratch
+	 * space without tripping verifier failures: see dt_cg_check_bounds.
+	 */
+	if (scratchsize > 0 &&
+	    create_gmap(dtp, "scratchmem", BPF_MAP_TYPE_PERCPU_ARRAY,
+			sizeof(uint32_t), scratchsize * 2, 1) == -1)
+		return -1;		/* dt_errno is set for us */
+
 	/*
 	 * We need to create the global (consolidated) string table.  We store
 	 * the actual length (for in-code BPF validation purposes) but augment
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index f32691f7c023..eaf8d6028275 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -204,6 +204,8 @@ dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act)
 	} while(0)
 
 	DT_CG_STORE_MAP_PTR("strtab", DCTX_STRTAB);
+	if (dtp->dt_options[DTRACEOPT_SCRATCHSIZE] > 0)
+		DT_CG_STORE_MAP_PTR("scratchmem", DCTX_SCRATCHMEM);
 	if (dt_idhash_datasize(dtp->dt_aggs) > 0)
 		DT_CG_STORE_MAP_PTR("aggs", DCTX_AGG);
 	if (dt_idhash_datasize(dtp->dt_globals) > 0)
@@ -372,6 +374,12 @@ dt_cg_call_clause(dtrace_hdl_t *dtp, dt_ident_t *idp, dt_clause_arg_t *arg)
 	emit(dlp,  BPF_LOAD(BPF_W, BPF_REG_0, BPF_REG_0, 0));
 	emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, arg->act, arg->lbl_exit));
 
+	/*
+	 *	dctx.mst->scratch_top = 8;
+	 *				// stw [%r7 + DMST_SCRATCH_TOP], 8
+	 */
+	emit(dlp,  BPF_STORE_IMM(BPF_W, BPF_REG_7, DMST_SCRATCH_TOP, 8));
+
 	/*
 	 *	dt_clause(dctx);	// mov %r1, %r9
 	 *				// call clause
@@ -3804,6 +3812,96 @@ dt_cg_subr_speculation(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 	TRACE_REGSET("    subr-speculation:End  ");
 }
 
+static void
+dt_cg_subr_alloca(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
+{
+	dt_node_t	*size = dnp->dn_args;
+	uint_t		lbl_ok = dt_irlist_label(dlp);
+	uint_t		lbl_err = dt_irlist_label(dlp);
+	int		opt_scratchsize = yypcb->pcb_hdl->dt_options[DTRACEOPT_SCRATCHSIZE];
+	int		mst, scratchbot, next;
+
+	TRACE_REGSET("    subr-alloca:Begin");
+
+	dt_cg_node(size, dlp, drp);
+
+	/*
+	 * Compile-error out if the size is too large even absent other
+	 * allocations.  (This prevents us generating code for which the
+	 * verifier will fail due to one branch of the conditional below being
+	 * determined to be unreachable.)
+	 *
+	 * We need to adjust the scratchsize to account for the first 8 bytes
+	 * that are used to represent the NULL pointer.
+	 */
+	if (size->dn_kind == DT_NODE_INT &&
+	    size->dn_value > opt_scratchsize - 8)
+		dnerror(dnp, D_ALLOCA_SIZE,
+			"alloca(%lu) size larger than scratchsize %lu\n",
+			(unsigned long) size->dn_value,
+			(unsigned long) opt_scratchsize - 8);
+
+	if (((dnp->dn_reg = dt_regset_alloc(drp)) == -1) ||
+	    ((mst = dt_regset_alloc(drp)) == -1) ||
+	    ((scratchbot = dt_regset_alloc(drp)) == -1) ||
+	    ((next = dt_regset_alloc(drp)) == -1))
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+	/*
+	 *     %mst = DT_STK_DCTX->mst;		// lddw %mst, [%fp + DCTX_FP(DCTX_MST)]
+	 *     %scratchbot = DCTX_SCRATCHMEM;	// lddw %scratchbot, [%fp + DT_STK_DCTX]
+	 *					// add %scratchbot, DCTX_SCRATCHMEM
+	 *     %dn_reg = DMST_SCRATCH_NEXT;	// lddw %dn_reg, [%mst + DMST_SCRATCH_NEXT]
+	 *     %top = %dn_reg + size;		// mov %top, %dn_reg
+	 *     					// add %top, %size
+	 *     %next = %top + 7 & -8;		// mov %next, %top
+	 *					// add %next, 7
+	 *					// and %next, -8
+	 *     if (%top > opt_scratchsize) ||	// jgt %top, opt_scratchsize, lbl_ok
+	 *	   (%scratchbot > opt_scratchsize) // jgt %scratchbot, opt_scratchsize, lbl_err
+	 *	   goto lbl_err;
+	 *     goto lbl_ok;
+	 * lbl_err:
+	 *     probe_error(DTRACEFLT_NOSCRATCH);
+	 *
+	 * lbl_ok:
+	 *     DMST_SCRATCH_TOP = %top;		// stdw [%mst + DMST_SCRATCH_TOP], %top
+	 *     DMST_SCRATCH_NEXT = %next;	// stdw [%mst + DMST_SCRATCH_NEXT], %next
+	 *     %dn_reg += %scratchbot		// add %dn_reg, %scratchbot
+	 *     (result: %dn_reg)
+	 *
+	 */
+
+	/* Loading.  */
+
+	emit(dlp,  BPF_LOAD(BPF_DW, mst, BPF_REG_FP, DT_STK_DCTX));
+	emit(dlp,  BPF_LOAD(BPF_DW, mst, mst, DCTX_MST));
+	emit(dlp,  BPF_LOAD(BPF_W, next, mst, DMST_SCRATCH_TOP));
+
+	/* Size testing and alignment.  */
+
+	emit(dlp,  BPF_MOV_REG(dnp->dn_reg, next));
+	emit(dlp,  BPF_ALU64_REG(BPF_ADD, next, size->dn_reg));
+	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, next, 7));
+	emit(dlp,  BPF_ALU64_IMM(BPF_AND, next, (int) -8));
+
+	emit(dlp,  BPF_BRANCH_IMM(BPF_JGT, next, opt_scratchsize + 8, lbl_err));
+	emit(dlp,  BPF_BRANCH_IMM(BPF_JLE, dnp->dn_reg, opt_scratchsize + 8,
+				  lbl_ok));
+	emitl(dlp, lbl_err,
+		   BPF_NOP());
+	dt_cg_probe_error(yypcb, DTRACEFLT_NOSCRATCH, DT_ISIMM, 0);
+	emitl(dlp, lbl_ok,
+		   BPF_STORE(BPF_W, mst, DMST_SCRATCH_TOP, next));
+
+	dt_regset_free(drp, mst);
+	dt_regset_free(drp, scratchbot);
+	dt_regset_free(drp, next);
+	dt_regset_free(drp, size->dn_reg);
+
+	TRACE_REGSET("    subr-alloca:End  ");
+}
+
 static void
 dt_cg_subr_strchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 {
@@ -4277,7 +4375,7 @@ static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
 	[DIF_SUBR_STRLEN]		= &dt_cg_subr_strlen,
 	[DIF_SUBR_COPYOUT]		= NULL,
 	[DIF_SUBR_COPYOUTSTR]		= NULL,
-	[DIF_SUBR_ALLOCA]		= NULL,
+	[DIF_SUBR_ALLOCA]		= &dt_cg_subr_alloca,
 	[DIF_SUBR_BCOPY]		= NULL,
 	[DIF_SUBR_COPYINTO]		= NULL,
 	[DIF_SUBR_MSGDSIZE]		= NULL,
diff --git a/libdtrace/dt_dctx.h b/libdtrace/dt_dctx.h
index d08534fe1da2..1ce7fc9100ea 100644
--- a/libdtrace/dt_dctx.h
+++ b/libdtrace/dt_dctx.h
@@ -22,6 +22,7 @@ typedef struct dt_mstate {
 	uint32_t	prid;		/* Probe ID */
 	uint32_t	clid;		/* Clause ID (unique per probe) */
 	uint32_t	tag;		/* Tag (for future use) */
+	uint32_t	scratch_top;	/* Current top of scratch space */
 	int32_t		syscall_errno;	/* syscall errno */
 	uint64_t	fault;		/* DTrace fault flags */
 	uint64_t	tstamp;		/* cached timestamp value */
@@ -29,15 +30,16 @@ typedef struct dt_mstate {
 	uint64_t	argv[10];	/* Probe arguments */
 } dt_mstate_t;
 
-#define DMST_EPID	offsetof(dt_mstate_t, epid)
-#define DMST_PRID	offsetof(dt_mstate_t, prid)
-#define DMST_CLID	offsetof(dt_mstate_t, clid)
-#define DMST_TAG	offsetof(dt_mstate_t, tag)
-#define DMST_ERRNO	offsetof(dt_mstate_t, syscall_errno)
-#define DMST_FAULT	offsetof(dt_mstate_t, fault)
-#define DMST_TSTAMP	offsetof(dt_mstate_t, tstamp)
-#define DMST_REGS	offsetof(dt_mstate_t, regs)
-#define DMST_ARG(n)	offsetof(dt_mstate_t, argv[n])
+#define DMST_EPID		offsetof(dt_mstate_t, epid)
+#define DMST_PRID		offsetof(dt_mstate_t, prid)
+#define DMST_CLID		offsetof(dt_mstate_t, clid)
+#define DMST_TAG		offsetof(dt_mstate_t, tag)
+#define DMST_SCRATCH_TOP	offsetof(dt_mstate_t, scratch_top)
+#define DMST_ERRNO		offsetof(dt_mstate_t, syscall_errno)
+#define DMST_FAULT		offsetof(dt_mstate_t, fault)
+#define DMST_TSTAMP		offsetof(dt_mstate_t, tstamp)
+#define DMST_REGS		offsetof(dt_mstate_t, regs)
+#define DMST_ARG(n)		offsetof(dt_mstate_t, argv[n])
 
 /*
  * The DTrace context.
@@ -48,6 +50,7 @@ typedef struct dt_dctx {
 	dt_mstate_t	*mst;		/* DTrace machine state */
 	char		*buf;		/* Output buffer scratch memory */
 	char		*mem;		/* General scratch memory */
+	char		*scratchmem;	/* Scratch space for alloca, etc */
 	char		*strtab;	/* String constant table */
 	char		*agg;		/* Aggregation data */
 	char		*gvars;		/* Global variables */
@@ -59,6 +62,7 @@ typedef struct dt_dctx {
 #define DCTX_MST	offsetof(dt_dctx_t, mst)
 #define DCTX_BUF	offsetof(dt_dctx_t, buf)
 #define DCTX_MEM	offsetof(dt_dctx_t, mem)
+#define DCTX_SCRATCHMEM	offsetof(dt_dctx_t, scratchmem)
 #define DCTX_STRTAB	offsetof(dt_dctx_t, strtab)
 #define DCTX_AGG	offsetof(dt_dctx_t, agg)
 #define DCTX_GVARS	offsetof(dt_dctx_t, gvars)
diff --git a/libdtrace/dt_dlibs.c b/libdtrace/dt_dlibs.c
index a6a5881ca509..e184babdac11 100644
--- a/libdtrace/dt_dlibs.c
+++ b/libdtrace/dt_dlibs.c
@@ -64,6 +64,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL(lvars, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(mem, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(probes, DT_IDENT_PTR),
+	DT_BPF_SYMBOL(scratchmem, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(specs, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(state, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(strtab, DT_IDENT_PTR),
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index f11d54d8d55a..e61d3db7d495 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -608,6 +608,7 @@ static const char *_dtrace_libdir = DTRACE_LIBDIR;  /* default library directory
 
 int _dtrace_strbuckets = 211;	/* default number of hash buckets (prime) */
 uint_t _dtrace_strsize = 256;	/* default size of string intrinsic type */
+uint_t _dtrace_scratchsize = 256;	/* default size of scratch space */
 uint_t _dtrace_stkindent = 14;	/* default whitespace indent for stack/ustack */
 uint_t _dtrace_pidbuckets = 64; /* default number of pid hash buckets */
 uint_t _dtrace_pidlrulim = 8;	/* default number of pid handles to cache */
@@ -793,6 +794,11 @@ dt_vopen(int version, int flags, int *errp,
 	dtp->dt_options[DTRACEOPT_SPECSIZE] = 1024 * 1024 * 4;
 	dtp->dt_options[DTRACEOPT_NSPEC] = 1;
 
+	/*
+	 * Set the maximum scratch space permitted.
+	 */
+	dtp->dt_options[DTRACEOPT_SCRATCHSIZE] = sizeof(uint64_t) + _dtrace_scratchsize;
+
 	/*
 	 * Set the default value of maxframes.
 	 */
diff --git a/libdtrace/dt_options.c b/libdtrace/dt_options.c
index 62f356faeb17..c24ceedb12cb 100644
--- a/libdtrace/dt_options.c
+++ b/libdtrace/dt_options.c
@@ -750,6 +750,21 @@ dt_opt_size(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
 	return 0;
 }
 
+static int
+dt_opt_scratchsize(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
+{
+	dtrace_optval_t val = 0;
+
+	if (arg != NULL && dt_optval_parse(arg, &val) != 0)
+		return dt_set_errno(dtp, EDT_BADOPTVAL);
+
+	if (val > 0)
+		val = P2ROUNDUP(val + sizeof(uint64_t), sizeof(uint64_t));
+
+	dtp->dt_options[option] = val;
+	return 0;
+}
+
 static int
 dt_opt_pcapsize(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
 {
@@ -1108,6 +1123,7 @@ static const dt_option_t _dtrace_rtoptions[] = {
 	{ "maxframes", dt_opt_runtime, DTRACEOPT_MAXFRAMES },
 	{ "nspec", dt_opt_runtime, DTRACEOPT_NSPEC },
 	{ "pcapsize", dt_opt_pcapsize, DTRACEOPT_PCAPSIZE },
+	{ "scratchsize", dt_opt_scratchsize, DTRACEOPT_SCRATCHSIZE },
 	{ "specsize", dt_opt_size, DTRACEOPT_SPECSIZE },
 	{ "stackframes", dt_opt_runtime, DTRACEOPT_STACKFRAMES },
 	{ "statusrate", dt_opt_rate, DTRACEOPT_STATUSRATE },
-- 
2.35.1




More information about the DTrace-devel mailing list