From 55f6421982acb3c9906dc3e70952f35c3eec05cd Mon Sep 17 00:00:00 2001 From: Xin LI Date: Tue, 1 Jul 2014 20:57:39 +0000 Subject: [PATCH] - Fix handling of "new" style of ioctl in compatiblity mode [1]; - Reorganize code and reduce diff from upstream; - Improve forward compatibility shims for previous kernel; Reported by: sbruno [1] X-MFC-With: r268075 --- .../lib/libzfs/common/libzfs_compat.c | 27 ++- .../opensolaris/common/zfs/zfs_ioctl_compat.c | 12 +- .../opensolaris/uts/common/fs/zfs/zfs_ioctl.c | 177 ++++++++++-------- 3 files changed, 127 insertions(+), 89 deletions(-) diff --git a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_compat.c b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_compat.c index 3c8119de5330..a3f6129876c9 100644 --- a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_compat.c +++ b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_compat.c @@ -72,16 +72,23 @@ zcmd_ioctl(int fd, int request, zfs_cmd_t *zc) if (zfs_ioctl_version == ZFS_IOCVER_UNDEF) zfs_ioctl_version = get_zfs_ioctl_version(); - if (zfs_ioctl_version == ZFS_IOCVER_LZC) - cflag = ZFS_CMD_COMPAT_LZC; - else if (zfs_ioctl_version == ZFS_IOCVER_DEADMAN) - cflag = ZFS_CMD_COMPAT_DEADMAN; - - /* - * If vfs.zfs.version.ioctl is not defined, assume we have v28 - * compatible binaries and use vfs.zfs.version.spa to test for v15 - */ - if (zfs_ioctl_version < ZFS_IOCVER_DEADMAN) { + if (zfs_ioctl_version >= ZFS_IOCVER_DEADMAN) { + switch (zfs_ioctl_version) { + case ZFS_IOCVER_ZCMD: + cflag = ZFS_CMD_COMPAT_ZCMD; + break; + case ZFS_IOCVER_LZC: + cflag = ZFS_CMD_COMPAT_LZC; + break; + case ZFS_IOCVER_DEADMAN: + cflag = ZFS_CMD_COMPAT_DEADMAN; + break; + } + } else { + /* + * If vfs.zfs.version.ioctl is not defined, assume we have v28 + * compatible binaries and use vfs.zfs.version.spa to test for v15 + */ cflag = ZFS_CMD_COMPAT_V28; if (zfs_spa_version < 0) diff --git a/sys/cddl/contrib/opensolaris/common/zfs/zfs_ioctl_compat.c b/sys/cddl/contrib/opensolaris/common/zfs/zfs_ioctl_compat.c index bdb6a9900836..62fed7b6ef27 100644 --- a/sys/cddl/contrib/opensolaris/common/zfs/zfs_ioctl_compat.c +++ b/sys/cddl/contrib/opensolaris/common/zfs/zfs_ioctl_compat.c @@ -697,6 +697,12 @@ zcmd_ioctl_compat(int fd, int request, zfs_cmd_t *zc, const int cflag) zp.zfs_cmd_size = sizeof(zfs_cmd_t); zp.zfs_ioctl_version = ZFS_IOCVER_CURRENT; return (ioctl(fd, ncmd, &zp)); + case ZFS_CMD_COMPAT_ZCMD: + ncmd = _IOWR('Z', request, struct zfs_iocparm); + zp.zfs_cmd = (uint64_t)zc; + zp.zfs_cmd_size = sizeof(zfs_cmd_zcmd_t); + zp.zfs_ioctl_version = ZFS_IOCVER_ZCMD; + return (ioctl(fd, ncmd, &zp)); case ZFS_CMD_COMPAT_LZC: ncmd = _IOWR('Z', request, struct zfs_cmd); return (ioctl(fd, ncmd, zc)); @@ -794,7 +800,8 @@ zfs_ioctl_compat_innvl(zfs_cmd_t *zc, nvlist_t * innvl, const int vec, char *poolname, *snapname; int err; - if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC) + if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC || + cflag == ZFS_CMD_COMPAT_ZCMD) goto out; switch (vec) { @@ -945,7 +952,8 @@ zfs_ioctl_compat_outnvl(zfs_cmd_t *zc, nvlist_t * outnvl, const int vec, { nvlist_t *tmpnvl; - if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC) + if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC || + cflag == ZFS_CMD_COMPAT_ZCMD) return (outnvl); switch (vec) { diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c index 3d5cd4c8f586..4308858843ae 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c @@ -24,6 +24,7 @@ * Copyright (c) 2011-2012 Pawel Jakub Dawidek . * All rights reserved. * Copyright 2013 Martin Matuska . All rights reserved. + * Copyright 2014 Xin Li . All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2014, Joyent, Inc. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. @@ -5922,6 +5923,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, zfs_iocparm_t *zc_iocparm; int cflag, cmd, oldvecnum; boolean_t newioc, compat; + void *compat_zc = NULL; cred_t *cr = td->td_ucred; #endif const zfs_ioc_vec_t *vec; @@ -5930,10 +5932,10 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, cflag = ZFS_CMD_COMPAT_NONE; compat = B_FALSE; - newioc = B_TRUE; + newioc = B_TRUE; /* "new" style (zfs_iocparm_t) ioctl */ len = IOCPARM_LEN(zcmd); - cmd = zcmd & 0xff; + vecnum = cmd = zcmd & 0xff; /* * Check if we are talking to supported older binaries @@ -5941,96 +5943,112 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, */ if (len != sizeof(zfs_iocparm_t)) { newioc = B_FALSE; - if (len == sizeof(zfs_cmd_t)) { - cflag = ZFS_CMD_COMPAT_LZC; - vecnum = cmd; - } else if (len == sizeof(zfs_cmd_deadman_t)) { - cflag = ZFS_CMD_COMPAT_DEADMAN; - compat = B_TRUE; - vecnum = cmd; - } else if (len == sizeof(zfs_cmd_v28_t)) { - cflag = ZFS_CMD_COMPAT_V28; - compat = B_TRUE; - vecnum = cmd; - } else if (len == sizeof(zfs_cmd_v15_t)) { - cflag = ZFS_CMD_COMPAT_V15; - compat = B_TRUE; - vecnum = zfs_ioctl_v15_to_v28[cmd]; - } else - return (EINVAL); - } else + compat = B_TRUE; + vecnum = cmd; + switch (len) { + case sizeof(zfs_cmd_zcmd_t): + cflag = ZFS_CMD_COMPAT_LZC; + break; + case sizeof(zfs_cmd_deadman_t): + cflag = ZFS_CMD_COMPAT_DEADMAN; + break; + case sizeof(zfs_cmd_v28_t): + cflag = ZFS_CMD_COMPAT_V28; + break; + case sizeof(zfs_cmd_v15_t): + cflag = ZFS_CMD_COMPAT_V15; + vecnum = zfs_ioctl_v15_to_v28[cmd]; + + /* + * Return without further handling + * if the command is blacklisted. + */ + if (vecnum == ZFS_IOC_COMPAT_PASS) + return (0); + else if (vecnum == ZFS_IOC_COMPAT_FAIL) + return (ENOTSUP); + break; + default: + return (EINVAL); + } + } + #ifdef illumos vecnum = cmd - ZFS_IOC_FIRST; ASSERT3U(getmajor(dev), ==, ddi_driver_major(zfs_dip)); #endif - if (compat) { - if (vecnum == ZFS_IOC_COMPAT_PASS) - return (0); - else if (vecnum == ZFS_IOC_COMPAT_FAIL) - return (ENOTSUP); - } - - /* - * Check if we have sufficient kernel memory allocated - * for the zfs_cmd_t request. Bail out if not so we - * will not access undefined memory region. - */ if (vecnum >= sizeof (zfs_ioc_vec) / sizeof (zfs_ioc_vec[0])) return (SET_ERROR(EINVAL)); vec = &zfs_ioc_vec[vecnum]; -#ifdef illumos zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP); - bzero(zc, sizeof(zfs_cmd_t)); +#ifdef illumos error = ddi_copyin((void *)arg, zc, sizeof (zfs_cmd_t), flag); if (error != 0) { error = SET_ERROR(EFAULT); goto out; } #else /* !illumos */ - /* - * We don't alloc/free zc only if talking to library ioctl version 2 - */ - if (cflag != ZFS_CMD_COMPAT_LZC) { - zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP); - bzero(zc, sizeof(zfs_cmd_t)); - } else { - zc = (void *)arg; - error = 0; - } + bzero(zc, sizeof(zfs_cmd_t)); if (newioc) { zc_iocparm = (void *)arg; - if (zc_iocparm->zfs_cmd_size != sizeof(zfs_cmd_t)) { - error = SET_ERROR(EFAULT); - goto out; - } - error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd, zc, - sizeof(zfs_cmd_t), flag); - if (error != 0) { - error = SET_ERROR(EFAULT); - goto out; - } - if (zc_iocparm->zfs_ioctl_version != ZFS_IOCVER_CURRENT) { - compat = B_TRUE; - switch (zc_iocparm->zfs_ioctl_version) { - case ZFS_IOCVER_ZCMD: - cflag = ZFS_CMD_COMPAT_ZCMD; - break; - default: + switch (zc_iocparm->zfs_ioctl_version) { + case ZFS_IOCVER_CURRENT: + if (zc_iocparm->zfs_cmd_size != sizeof(zfs_cmd_t)) { error = SET_ERROR(EINVAL); goto out; } + break; + case ZFS_IOCVER_ZCMD: + if (zc_iocparm->zfs_cmd_size > sizeof(zfs_cmd_t) || + zc_iocparm->zfs_cmd_size < sizeof(zfs_cmd_zcmd_t)) { + error = SET_ERROR(EFAULT); + goto out; + } + compat = B_TRUE; + cflag = ZFS_CMD_COMPAT_ZCMD; + break; + default: + error = SET_ERROR(EINVAL); + goto out; + /* NOTREACHED */ + } + + if (compat) { + ASSERT(sizeof(zfs_cmd_t) >= zc_iocparm->zfs_cmd_size); + compat_zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP); + bzero(compat_zc, sizeof(zfs_cmd_t)); + + error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd, + compat_zc, zc_iocparm->zfs_cmd_size, flag); + if (error != 0) { + error = SET_ERROR(EFAULT); + goto out; + } + } else { + error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd, + zc, zc_iocparm->zfs_cmd_size, flag); + if (error != 0) { + error = SET_ERROR(EFAULT); + goto out; + } } } if (compat) { - zfs_cmd_compat_get(zc, arg, cflag); + if (newioc) { + ASSERT(compat_zc != NULL); + zfs_cmd_compat_get(zc, compat_zc, cflag); + } else { + ASSERT(compat_zc == NULL); + zfs_cmd_compat_get(zc, arg, cflag); + } oldvecnum = vecnum; error = zfs_ioctl_compat_pre(zc, &vecnum, cflag); if (error != 0) @@ -6126,7 +6144,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, fnvlist_free(lognv); /* rewrite outnvl for backwards compatibility */ - if (cflag != ZFS_CMD_COMPAT_NONE && cflag != ZFS_CMD_COMPAT_LZC) + if (compat) outnvl = zfs_ioctl_compat_outnvl(zc, outnvl, vecnum, cflag); @@ -6151,17 +6169,30 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, out: nvlist_free(innvl); - if (compat) { - zfs_ioctl_compat_post(zc, cmd, cflag); - zfs_cmd_compat_put(zc, arg, vecnum, cflag); - } - #ifdef illumos rc = ddi_copyout(zc, (void *)arg, sizeof (zfs_cmd_t), flag); if (error == 0 && rc != 0) error = SET_ERROR(EFAULT); #else - if (newioc) { + if (compat) { + zfs_ioctl_compat_post(zc, cmd, cflag); + if (newioc) { + ASSERT(compat_zc != NULL); + ASSERT(sizeof(zfs_cmd_t) >= zc_iocparm->zfs_cmd_size); + + zfs_cmd_compat_put(zc, compat_zc, vecnum, cflag); + rc = ddi_copyout(compat_zc, + (void *)(uintptr_t)zc_iocparm->zfs_cmd, + zc_iocparm->zfs_cmd_size, flag); + if (error == 0 && rc != 0) + error = SET_ERROR(EFAULT); + kmem_free(compat_zc, sizeof (zfs_cmd_t)); + } else { + zfs_cmd_compat_put(zc, arg, vecnum, cflag); + } + } else { + ASSERT(newioc); + rc = ddi_copyout(zc, (void *)(uintptr_t)zc_iocparm->zfs_cmd, sizeof (zfs_cmd_t), flag); if (error == 0 && rc != 0) @@ -6178,15 +6209,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag, strfree(saved_poolname); } -#ifdef illumos kmem_free(zc, sizeof (zfs_cmd_t)); -#else - /* - * We don't alloc/free zc only if talking to library ioctl version 2 - */ - if (cflag != ZFS_CMD_COMPAT_LZC) - kmem_free(zc, sizeof (zfs_cmd_t)); -#endif return (error); }