[DTrace-devel] [PATCH v3 19/19] alloca: allow passing alloca pointers to actions and subrs
Nick Alcock
nick.alcock at oracle.com
Thu Mar 24 18:24:45 UTC 2022
A great many subrs and a good few actions allow the passing-in of
pointers (mostly, but not entirely, char *) which may be in alloca()ed
space. Since they then usually go on to dereference these pointers,
the pointers must be bounds-checked.
Since most of them are char *, we need to check the string length
against the bound, which means we need to figure it out. So split out
the guts of strlen into a new dt_cg_strlen() and call it both from
dt_cg_subr_strlen() and from a new dt_cg_check_alloca_string_bounds(),
which is called from everywhere where an incoming char * arg might be
an alloca'ed pointer. Most of these are just ordinary subrs, but there
are a few surprising places: dt_cg_store_val, which is called to
store things passed to actions which might be in alloca()ed space
(both strings and scalars); and codegen for stringof(), since the
arg to stringof() might be in alloca()ed space too.
A new test that tests more or less all of this is added: it has no
expected results because all we actually care about is that there are no
verifier failures.
Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
libdtrace/dt_cg.c | 89 ++++++++++---
test/unittest/funcs/alloca/tst.alloca-funcs.d | 125 ++++++++++++++++++
2 files changed, 198 insertions(+), 16 deletions(-)
create mode 100644 test/unittest/funcs/alloca/tst.alloca-funcs.d
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 59e8c206979a..730248c7825d 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -28,6 +28,9 @@ static void dt_cg_node(dt_node_t *, dt_irlist_t *, dt_regset_t *);
static void _dt_unused_
dt_cg_trace(dt_irlist_t *dlp _dt_unused_, dt_regset_t *drp _dt_unused_,
int isreg _dt_unused_, int counter _dt_unused_, uint64_t val _dt_unused_);
+static void dt_cg_check_scratch_bounds(dt_irlist_t *dlp, dt_regset_t *drp, int reg,
+ int size, int sizemax);
+static void dt_cg_strlen(dt_irlist_t *dlp, dt_regset_t *drp, int str);
/*
* The value stored in an alloca'ed variable if what was stored is literal
@@ -765,6 +768,25 @@ dt_cg_check_notnull(dt_irlist_t *dlp, dt_regset_t *drp, int reg)
BPF_NOP());
}
+/*
+ * Check the bounds of the string in node STR, if need be (if it is in alloca()ed
+ * space). Should be called for every node that is an argument to a function
+ * and that might come from alloca'ed space.
+ */
+static void
+dt_cg_check_alloca_string_bounds(dt_irlist_t *dlp, dt_regset_t *drp, dt_node_t *str)
+{
+ int opt_scratchsize = yypcb->pcb_hdl->dt_options[DTRACEOPT_SCRATCHSIZE];
+
+ if (!(str->dn_flags & DT_NF_ALLOCA))
+ return;
+
+ dt_regset_xalloc(drp, BPF_REG_0);
+ dt_cg_strlen(dlp, drp, str->dn_reg);
+ dt_cg_check_scratch_bounds(dlp, drp, str->dn_reg, BPF_REG_0, opt_scratchsize);
+ dt_regset_free(drp, BPF_REG_0);
+}
+
/*
* Check whether mst->fault indicates a fault was triggered. If so, abort the
* current clause by means of a straight jump to the exit label.
@@ -973,6 +995,25 @@ dt_cg_tstring_free(dt_pcb_t *pcb, dt_node_t *dnp)
}
}
+/*
+ * Generate code to take the length of the string in reg STR: return it in
+ * BPF_REG_0 (which must be xalloced before the call).
+ */
+static void
+dt_cg_strlen(dt_irlist_t *dlp, dt_regset_t *drp, int str)
+{
+ dt_ident_t *idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_strlen");
+
+ assert(idp != NULL);
+
+ if (dt_regset_xalloc_args(drp) == -1)
+ longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+ emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
+ emit(dlp, BPF_MOV_REG(BPF_REG_2, str));
+ emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+ dt_regset_free_args(drp);
+}
+
static const uint_t ldstw[] = {
0,
BPF_B, BPF_H, 0, BPF_W,
@@ -1061,6 +1102,7 @@ dt_cg_store_val(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind,
size_t strsize = pcb->pcb_hdl->dt_options[DTRACEOPT_STRSIZE];
dt_cg_check_notnull(dlp, drp, dnp->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, dnp);
TRACE_REGSET("store_val(): Begin ");
off = dt_rec_add(pcb->pcb_hdl, dt_cg_fill_gap, kind, size + 1,
@@ -1096,6 +1138,12 @@ dt_cg_store_val(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind,
TRACE_REGSET("store_val(): Begin ");
dt_cg_check_notnull(dlp, drp, dnp->dn_reg);
+ if (dnp->dn_flags & DT_NF_ALLOCA) {
+ int opt_scratchsize = yypcb->pcb_hdl->dt_options[DTRACEOPT_SCRATCHSIZE];
+ dt_cg_check_scratch_bounds(dlp, drp, dnp->dn_reg, size,
+ opt_scratchsize);
+ }
+
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4105,9 +4153,10 @@ dt_cg_subr_path_helper(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
TRACE_REGSET(" subr-path_helper:Begin");
dt_cg_node(str, dlp, drp);
dt_cg_check_notnull(dlp, drp, str->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
/*
- * The result needs be be a temporary string, so we request one.
+ * The result needs to be a temporary string, so we request one.
*/
dnp->dn_reg = dt_regset_alloc(drp);
if (dnp->dn_reg == -1)
@@ -4169,6 +4218,9 @@ dt_cg_subr_index(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
if (start != NULL)
dt_cg_node(start, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, s);
+ dt_cg_check_alloca_string_bounds(dlp, drp, t);
+
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
emit(dlp, BPF_MOV_REG(BPF_REG_1, s->dn_reg));
@@ -4285,6 +4337,9 @@ dt_cg_subr_rindex(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
if (start != NULL)
dt_cg_node(start, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, s);
+ dt_cg_check_alloca_string_bounds(dlp, drp, t);
+
/* start setting up function call to BPF function dt_rindex() */
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4540,6 +4595,7 @@ dt_cg_subr_strchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_cg_node(str, dlp, drp);
dt_cg_check_notnull(dlp, drp, str->dn_reg);
dt_cg_node(chr, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4551,7 +4607,7 @@ dt_cg_subr_strchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_regset_free(drp, chr->dn_reg);
/*
- * The result needs be be a temporary string, so we request one.
+ * The result needs to be a temporary string, so we request one.
*/
dnp->dn_reg = dt_regset_alloc(drp);
if (dnp->dn_reg == -1)
@@ -4596,6 +4652,7 @@ dt_cg_subr_strrchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_cg_node(str, dlp, drp);
dt_cg_check_notnull(dlp, drp, str->dn_reg);
dt_cg_node(chr, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4607,7 +4664,7 @@ dt_cg_subr_strrchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_regset_free(drp, chr->dn_reg);
/*
- * The result needs be be a temporary string, so we request one.
+ * The result needs to be a temporary string, so we request one.
*/
dnp->dn_reg = dt_regset_alloc(drp);
if (dnp->dn_reg == -1)
@@ -4637,28 +4694,20 @@ dt_cg_subr_strrchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
static void
dt_cg_subr_strlen(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
{
- dt_ident_t *idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_strlen");
dt_node_t *str = dnp->dn_args;
- assert(idp != NULL);
-
TRACE_REGSET(" subr-strlen:Begin");
dt_cg_node(str, dlp, drp);
dt_cg_check_notnull(dlp, drp, str->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
dnp->dn_reg = str->dn_reg; /* re-use register */
- if (dt_regset_xalloc_args(drp) == -1)
- longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
-
- emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
- emit(dlp, BPF_MOV_REG(BPF_REG_2, str->dn_reg));
- dt_regset_free(drp, str->dn_reg);
- dt_cg_tstring_free(yypcb, str);
dt_regset_xalloc(drp, BPF_REG_0);
- emite(dlp,BPF_CALL_FUNC(idp->di_id), idp);
+ dt_cg_strlen(dlp, drp, str->dn_reg);
+ dt_cg_tstring_free(yypcb, str);
- dt_regset_free_args(drp);
+ dt_regset_free (drp, str->dn_reg);
dnp->dn_reg = dt_regset_alloc(drp);
if (dnp->dn_reg == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4683,9 +4732,11 @@ dt_cg_subr_strjoin(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_cg_check_notnull(dlp, drp, s1->dn_reg);
dt_cg_node(s2, dlp, drp);
dt_cg_check_notnull(dlp, drp, s2->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, s1);
+ dt_cg_check_alloca_string_bounds(dlp, drp, s2);
/*
- * The result needs be be a temporary string, so we request one.
+ * The result needs to be a temporary string, so we request one.
*/
dnp->dn_reg = dt_regset_alloc(drp);
if (dnp->dn_reg == -1)
@@ -4730,6 +4781,8 @@ dt_cg_subr_strstr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_cg_check_notnull(dlp, drp, s->dn_reg);
dt_cg_node(t, dlp, drp);
dt_cg_check_notnull(dlp, drp, t->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, s);
+ dt_cg_check_alloca_string_bounds(dlp, drp, t);
/* call dt_index() call */
if (dt_regset_xalloc_args(drp) == -1)
@@ -4831,6 +4884,7 @@ dt_cg_subr_strtok(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
/* string is present: copy it to internal state */
dt_cg_node(str, dlp, drp);
dt_cg_check_notnull(dlp, drp, str->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
@@ -4860,6 +4914,7 @@ dt_cg_subr_strtok(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
/* get delimiters */
dt_cg_node(del, dlp, drp);
dt_cg_check_notnull(dlp, drp, del->dn_reg);
+ dt_cg_check_alloca_string_bounds(dlp, drp, del);
/* allocate temporary string for result */
dnp->dn_reg = dt_regset_alloc(drp);
@@ -4914,6 +4969,7 @@ dt_cg_subr_substr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
dt_cg_node(idx, dlp, drp);
if (cnt != NULL)
dt_cg_node(cnt, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, str);
/*
* Allocate a temporary string slot for the result.
@@ -5363,6 +5419,7 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
case DT_TOK_STRINGOF:
dt_cg_node(dnp->dn_child, dlp, drp);
+ dt_cg_check_alloca_string_bounds(dlp, drp, dnp->dn_child);
dnp->dn_reg = dnp->dn_child->dn_reg;
break;
diff --git a/test/unittest/funcs/alloca/tst.alloca-funcs.d b/test/unittest/funcs/alloca/tst.alloca-funcs.d
new file mode 100644
index 000000000000..eca281ac5f70
--- /dev/null
+++ b/test/unittest/funcs/alloca/tst.alloca-funcs.d
@@ -0,0 +1,125 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Function calls to alloca()ed space work.
+ *
+ * SECTION: Actions and Subroutines/alloca()
+ */
+
+#pragma D option quiet
+
+/*
+ * Everything is in an independent clause with its own alloca(),
+ * to try to make sure that the verifier's bound proofs don't leak
+ * from one test to the next.
+ */
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ printf("%s\n", stringof(x));
+ trace(x);
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(basename(x));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ y = (char *) alloca(8);
+ x[0] = 0;
+ y[0] = 0;
+ trace(index(x, y));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ y = (char *) alloca(8);
+ x[0] = 0;
+ y[0] = 0;
+ trace(rindex(x, y));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(strchr(x, 0));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(strrchr(x, 0));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(strlen(x));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ y = (char *) alloca(8);
+ x[0] = 0;
+ y[0] = 0;
+ trace(strjoin(x, y));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ y = (char *) alloca(8);
+ x[0] = 0;
+ y[0] = 0;
+ trace(strstr(x, y));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ y = (char *) alloca(8);
+ x[0] = 0;
+ y[0] = 0;
+ trace(strtok(x, y));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(strtok(NULL, x));
+}
+
+BEGIN
+{
+ x = (char *) alloca(8);
+ x[0] = 0;
+ trace(substr(x, 0, 1));
+}
+
+BEGIN
+{
+ exit(0);
+}
+
+ERROR
+{
+ exit(0);
+}
--
2.35.1.261.g8402f930ba.dirty
More information about the DTrace-devel
mailing list