diff options
-rw-r--r-- | sys/dev/netmap/netmap.c | 69 | ||||
-rw-r--r-- | tests/sys/netmap/ctrl-api-test.c | 23 |
2 files changed, 63 insertions, 29 deletions
diff --git a/sys/dev/netmap/netmap.c b/sys/dev/netmap/netmap.c index acc05513fbb2..392c00502699 100644 --- a/sys/dev/netmap/netmap.c +++ b/sys/dev/netmap/netmap.c @@ -3369,7 +3369,7 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) size_t rqsz, optsz, bufsz; int error = 0; char *ker = NULL, *p; - struct nmreq_option **next, *src, **opt_tab; + struct nmreq_option **next, *src, **opt_tab, *opt; uint64_t *ptrs; if (hdr->nr_reserved) { @@ -3420,24 +3420,36 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) *ptrs++ = hdr->nr_body; *ptrs++ = hdr->nr_options; p = (char *)ptrs; + /* overwrite the user pointer with the in-kernel one */ + hdr->nr_body = (uintptr_t)p; + /* prepare the options-list pointers and temporarily terminate + * the in-kernel list, in case we have to jump to out_restore + */ + next = (struct nmreq_option **)&hdr->nr_options; + src = *next; + hdr->nr_options = 0; /* copy the body */ - error = copyin((void *)(uintptr_t)hdr->nr_body, p, rqsz); + error = copyin(*(void **)ker, p, rqsz); if (error) goto out_restore; - /* overwrite the user pointer with the in-kernel one */ - hdr->nr_body = (uintptr_t)p; p += rqsz; /* start of the options table */ opt_tab = (struct nmreq_option **)p; p += sizeof(opt_tab) * NETMAP_REQ_OPT_MAX; /* copy the options */ - next = (struct nmreq_option **)&hdr->nr_options; - src = *next; while (src) { - struct nmreq_option *opt; + struct nmreq_option *nsrc; + if (p - ker + sizeof(uint64_t*) + sizeof(*src) > bufsz) { + error = EMSGSIZE; + /* there might be a loop in the list: don't try to + * copyout the options + */ + hdr->nr_options = 0; + goto out_restore; + } /* copy the option header */ ptrs = (uint64_t *)p; opt = (struct nmreq_option *)(ptrs + 1); @@ -3445,15 +3457,19 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) if (error) goto out_restore; rqsz += sizeof(*src); + p = (char *)(opt + 1); + /* make a copy of the user next pointer */ *ptrs = opt->nro_next; - /* overwrite the user pointer with the in-kernel one */ + /* append the option to the in-kernel list */ *next = opt; - - /* initialize the option as not supported. - * Recognized options will update this field. + /* temporarily teminate the in-kernel list, in case we have to + * jump to out_restore */ - opt->nro_status = EOPNOTSUPP; + nsrc = (struct nmreq_option *)opt->nro_next; + opt->nro_next = 0; + + opt->nro_status = 0; /* check for invalid types */ if (opt->nro_reqtype < 1) { @@ -3461,12 +3477,11 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) nm_prinf("invalid option type: %u", opt->nro_reqtype); opt->nro_status = EINVAL; error = EINVAL; - goto next; + goto out_restore; } if (opt->nro_reqtype >= NETMAP_REQ_OPT_MAX) { - /* opt->nro_status is already EOPNOTSUPP */ - error = EOPNOTSUPP; + /* opt->nro_status will be set to EOPNOTSUPP */ goto next; } @@ -3479,12 +3494,10 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) opt->nro_status = EINVAL; opt_tab[opt->nro_reqtype]->nro_status = EINVAL; error = EINVAL; - goto next; + goto out_restore; } opt_tab[opt->nro_reqtype] = opt; - p = (char *)(opt + 1); - /* copy the option body */ optsz = nmreq_opt_size_by_type(opt->nro_reqtype, opt->nro_size); @@ -3507,18 +3520,20 @@ nmreq_copyin(struct nmreq_header *hdr, int nr_body_is_user) next: /* move to next option */ next = (struct nmreq_option **)&opt->nro_next; - src = *next; + src = nsrc; } - if (error) - nmreq_copyout(hdr, error); - return error; + + /* initialize all the options as not supported. Recognized options + * will update their field. + */ + for (src = (struct nmreq_option *)hdr->nr_options; src; + src = (struct nmreq_option *)src->nro_next) { + src->nro_status = EOPNOTSUPP; + } + return 0; out_restore: - ptrs = (uint64_t *)ker; - hdr->nr_body = *ptrs++; - hdr->nr_options = *ptrs++; - hdr->nr_reserved = 0; - nm_os_free(ker); + nmreq_copyout(hdr, error); out_err: return error; } diff --git a/tests/sys/netmap/ctrl-api-test.c b/tests/sys/netmap/ctrl-api-test.c index 8c87760217c3..c143d14ae3ab 100644 --- a/tests/sys/netmap/ctrl-api-test.c +++ b/tests/sys/netmap/ctrl-api-test.c @@ -1012,9 +1012,10 @@ infinite_options(struct TestContext *ctx) { struct nmreq_option opt; - printf("Testing infinite list of options on %s\n", ctx->ifname_ext); + printf("Testing infinite list of options on %s (invalid options)\n", ctx->ifname_ext); - opt.nro_reqtype = 1234; + memset(&opt, 0, sizeof(opt)); + opt.nro_reqtype = NETMAP_REQ_OPT_MAX + 1; push_option(&opt, ctx); opt.nro_next = (uintptr_t)&opt; if (port_register_hwall(ctx) >= 0) @@ -1023,6 +1024,23 @@ infinite_options(struct TestContext *ctx) return (errno == EMSGSIZE ? 0 : -1); } +static int +infinite_options2(struct TestContext *ctx) +{ + struct nmreq_option opt; + + printf("Testing infinite list of options on %s (valid options)\n", ctx->ifname_ext); + + memset(&opt, 0, sizeof(opt)); + opt.nro_reqtype = NETMAP_REQ_OPT_OFFSETS; + push_option(&opt, ctx); + opt.nro_next = (uintptr_t)&opt; + if (port_register_hwall(ctx) >= 0) + return -1; + clear_options(ctx); + return (errno == EINVAL ? 0 : -1); +} + #ifdef CONFIG_NETMAP_EXTMEM int change_param(const char *pname, unsigned long newv, unsigned long *poldv) @@ -2049,6 +2067,7 @@ static struct mytest tests[] = { decltest(vale_polling_enable_disable), decltest(unsupported_option), decltest(infinite_options), + decltest(infinite_options2), #ifdef CONFIG_NETMAP_EXTMEM decltest(extmem_option), decltest(bad_extmem_option), |