nvlist_pack() and nvlist_unpack are implemented recursively, which can
cause the stack to overflow with a deeply nested nvlist; i.e. an nvlist
which contains an nvlist, which contains an nvlist, which...
Unprivileged users can pass an nvlist to the kernel via certain ioctls
on /dev/zfs, which the kernel will unpack without additional permission
checking or validation. Therefore, an unprivileged user can cause the
kernel's stack to overflow and panic.
Ideally, these functions would be implemented non-recursively. As a
quick fix, this patch limits the depth of the recursion and returns an
error when attempting to pack and unpack a deeply-nested nvlist.
Signed-off-by: Adam Leventhal <ahl@delphix.com>
Signed-off-by: George Wilson <george.wilson@delphix.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Ported-by: Prakash Surya <prakash.surya@delphix.com>
OpenZFS-issue: https://www.illumos.org/issues/7263
OpenZFS-commit: https://github.com/openzfs/openzfs/commit/
0511d6d
-
/*
* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
/*
* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016 by Delphix. All rights reserved.
*/
#include <sys/stropts.h>
*/
#include <sys/stropts.h>
#define NVPAIR2I_NVP(nvp) \
((i_nvp_t *)((size_t)(nvp) - offsetof(i_nvp_t, nvi_nvp)))
#define NVPAIR2I_NVP(nvp) \
((i_nvp_t *)((size_t)(nvp) - offsetof(i_nvp_t, nvi_nvp)))
+#ifdef _KERNEL
+int nvpair_max_recursion = 20;
+#else
+int nvpair_max_recursion = 100;
+#endif
int
nv_alloc_init(nv_alloc_t *nva, const nv_alloc_ops_t *nvo, /* args */ ...)
int
nv_alloc_init(nv_alloc_t *nva, const nv_alloc_ops_t *nvo, /* args */ ...)
const nvs_ops_t *nvs_ops;
void *nvs_private;
nvpriv_t *nvs_priv;
const nvs_ops_t *nvs_ops;
void *nvs_private;
nvpriv_t *nvs_priv;
nvs_embedded(nvstream_t *nvs, nvlist_t *embedded)
{
switch (nvs->nvs_op) {
nvs_embedded(nvstream_t *nvs, nvlist_t *embedded)
{
switch (nvs->nvs_op) {
- case NVS_OP_ENCODE:
- return (nvs_operation(nvs, embedded, NULL));
+ case NVS_OP_ENCODE: {
+ int err;
+ if (nvs->nvs_recursion >= nvpair_max_recursion)
+ return (EINVAL);
+ nvs->nvs_recursion++;
+ err = nvs_operation(nvs, embedded, NULL);
+ nvs->nvs_recursion--;
+ return (err);
+ }
case NVS_OP_DECODE: {
nvpriv_t *priv;
int err;
case NVS_OP_DECODE: {
nvpriv_t *priv;
int err;
nvlist_init(embedded, embedded->nvl_nvflag, priv);
nvlist_init(embedded, embedded->nvl_nvflag, priv);
+ if (nvs->nvs_recursion >= nvpair_max_recursion)
+ return (EINVAL);
+ nvs->nvs_recursion++;
if ((err = nvs_operation(nvs, embedded, NULL)) != 0)
nvlist_free(embedded);
if ((err = nvs_operation(nvs, embedded, NULL)) != 0)
nvlist_free(embedded);
return (EINVAL);
nvs.nvs_op = nvs_op;
return (EINVAL);
nvs.nvs_op = nvs_op;
/*
* For NVS_OP_ENCODE and NVS_OP_DECODE make sure an nvlist and
/*
* For NVS_OP_ENCODE and NVS_OP_DECODE make sure an nvlist and