On Tue, Dec 13, 2022 at 5:01 PM Toke Høiland-Jørgensen <toke@xxxxxxxxxx> wrote: > > The commit in the Fixes tag refactored the check for zeroed memory in > libbpf_validate_opts() into a separate libbpf_is_mem_zeroed() function. > This function has a 'len' argument of the signed 'ssize_t' type, which in > both callers is computed by subtracting two unsigned size_t values from > each other. In both subtractions, one of the values being subtracted is > converted to 'ssize_t', while the other stays 'size_t'. > > The problem with this is that, because both sizes are the same > rank ('ssize_t' is defined as 'long' and 'size_t' is 'unsigned long'), the > type of the mixed-sign arithmetic operation ends up being converted back to > unsigned. This means it can underflow if the user-specified size in > opts->sz is smaller than the size of the type as defined by libbpf. If that > happens, it will cause out-of-bounds reads in libbpf_is_mem_zeroed(). hmm... but libbpf_is_mem_zeroed expects signed ssize_t, so that "underflow" will turn into a proper negative ssize_t value. What am I missing? Seems to be working fine: $ cat test.c #include <stdio.h> void testit(ssize_t sz) { printf("%zd\n", sz); } int main() { ssize_t slarge = 100; size_t ularge = 100; ssize_t ssmall = 50; size_t usmall = 50; testit(ssmall - slarge); testit(ssmall - ularge); testit(usmall - slarge); testit(usmall - ularge); } $ cc test.c && ./a.out -50 -50 -50 -50 > > To fix this, change libbpf_is_mem_zeroed() to take unsigned start and end > offsets instead of a signed length. This avoids all casts between signed > and unsigned types and should hopefully prevent a similar error from > reappearing in the future. > > Fixes: 3ec84f4b1638 ("libbpf: Add bpf_cookie support to bpf_link_create() API") > Signed-off-by: Toke Høiland-Jørgensen <toke@xxxxxxxxxx> > --- > tools/lib/bpf/libbpf_internal.h | 20 ++++++++++---------- > 1 file changed, 10 insertions(+), 10 deletions(-) > > diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h > index 377642ff51fc..92375a86b15c 100644 > --- a/tools/lib/bpf/libbpf_internal.h > +++ b/tools/lib/bpf/libbpf_internal.h > @@ -267,13 +267,14 @@ void *libbpf_add_mem(void **data, size_t *cap_cnt, size_t elem_sz, > size_t cur_cnt, size_t max_cnt, size_t add_cnt); > int libbpf_ensure_mem(void **data, size_t *cap_cnt, size_t elem_sz, size_t need_cnt); > > -static inline bool libbpf_is_mem_zeroed(const char *p, ssize_t len) > +static inline bool libbpf_is_mem_zeroed(const char *obj, > + size_t off_start, size_t off_end) > { > - while (len > 0) { > + const char *p; > + > + for (p = obj + off_start; p < obj + off_end; p++) { > if (*p) > return false; > - p++; > - len--; > } > return true; > } > @@ -286,7 +287,7 @@ static inline bool libbpf_validate_opts(const char *opts, > pr_warn("%s size (%zu) is too small\n", type_name, user_sz); > return false; > } > - if (!libbpf_is_mem_zeroed(opts + opts_sz, (ssize_t)user_sz - opts_sz)) { > + if (!libbpf_is_mem_zeroed(opts, opts_sz, user_sz)) { > pr_warn("%s has non-zero extra bytes\n", type_name); > return false; > } > @@ -309,11 +310,10 @@ static inline bool libbpf_validate_opts(const char *opts, > } while (0) > > #define OPTS_ZEROED(opts, last_nonzero_field) \ > -({ \ > - ssize_t __off = offsetofend(typeof(*(opts)), last_nonzero_field); \ > - !(opts) || libbpf_is_mem_zeroed((const void *)opts + __off, \ > - (opts)->sz - __off); \ > -}) > + (!(opts) || libbpf_is_mem_zeroed((const void *)opts, \ > + offsetofend(typeof(*(opts)), \ > + last_nonzero_field), \ > + (opts)->sz)) > > enum kern_feature_id { > /* v4.14: kernel support for program & map names. */ > -- > 2.38.1 >