mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
file reference counts can get corrupted
Callers of zfs_file_get and zfs_file_put can corrupt the reference counts for the file structure resulting in a panic or a soft lockup. When zfs send/recv runs, it will add a reference count to the open file, and begin to send or recv the stream. If the file descriptor is closed, then when dmu_recv_stream() or dmu_send() return we will call zfs_file_put to remove the reference we placed on the file structure. Unfortunately, because zfs_file_put() uses the file descriptor to lookup the file structure, it may end up finding that the file descriptor table no longer contains the file struct, thus leaking the file structure. Or it might end up finding a file descriptor for a different file and blindly updating its reference counts. Other failure modes probably exists. This change reworks the zfs_file_[get|put] interface to not rely on the file descriptor but instead pass the zfs_file_t pointer around. Reviewed-by: Matthew Ahrens <mahrens@delphix.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Mark Maybee <mark.maybee@delphix.com> Reviewed-by: Ryan Moeller <ryan@iXsystems.com> Co-authored-by: Allan Jude <allan@klarasystems.com> Signed-off-by: George Wilson <gwilson@delphix.com> External-issue: DLPX-76119 Closes #12299
This commit is contained in:
committed by
Tony Hutter
parent
04ebe29188
commit
8415c3c170
@@ -241,28 +241,21 @@ zfs_file_fsync(zfs_file_t *fp, int flags)
|
||||
return (zfs_vop_fsync(fp->f_vnode));
|
||||
}
|
||||
|
||||
int
|
||||
zfs_file_get(int fd, zfs_file_t **fpp)
|
||||
zfs_file_t *
|
||||
zfs_file_get(int fd)
|
||||
{
|
||||
struct file *fp;
|
||||
|
||||
if (fget(curthread, fd, &cap_no_rights, &fp))
|
||||
return (SET_ERROR(EBADF));
|
||||
return (NULL);
|
||||
|
||||
*fpp = fp;
|
||||
return (0);
|
||||
return (fp);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_file_put(int fd)
|
||||
zfs_file_put(zfs_file_t *fp)
|
||||
{
|
||||
struct file *fp;
|
||||
|
||||
/* No CAP_ rights required, as we're only releasing. */
|
||||
if (fget(curthread, fd, &cap_no_rights, &fp) == 0) {
|
||||
fdrop(fp, curthread);
|
||||
fdrop(fp, curthread);
|
||||
}
|
||||
fdrop(fp, curthread);
|
||||
}
|
||||
|
||||
loff_t
|
||||
|
||||
@@ -407,36 +407,22 @@ zfs_file_unlink(const char *path)
|
||||
* Get reference to file pointer
|
||||
*
|
||||
* fd - input file descriptor
|
||||
* fpp - pointer to file pointer
|
||||
*
|
||||
* Returns 0 on success EBADF on failure.
|
||||
* Returns pointer to file struct or NULL
|
||||
*/
|
||||
int
|
||||
zfs_file_get(int fd, zfs_file_t **fpp)
|
||||
zfs_file_t *
|
||||
zfs_file_get(int fd)
|
||||
{
|
||||
zfs_file_t *fp;
|
||||
|
||||
fp = fget(fd);
|
||||
if (fp == NULL)
|
||||
return (EBADF);
|
||||
|
||||
*fpp = fp;
|
||||
|
||||
return (0);
|
||||
return (fget(fd));
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop reference to file pointer
|
||||
*
|
||||
* fd - input file descriptor
|
||||
* fp - input file struct pointer
|
||||
*/
|
||||
void
|
||||
zfs_file_put(int fd)
|
||||
zfs_file_put(zfs_file_t *fp)
|
||||
{
|
||||
struct file *fp;
|
||||
|
||||
if ((fp = fget(fd)) != NULL) {
|
||||
fput(fp);
|
||||
fput(fp);
|
||||
}
|
||||
fput(fp);
|
||||
}
|
||||
|
||||
+12
-8
@@ -278,25 +278,29 @@ zfs_zevent_minor_to_state(minor_t minor, zfs_zevent_t **ze)
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
zfs_file_t *
|
||||
zfs_zevent_fd_hold(int fd, minor_t *minorp, zfs_zevent_t **ze)
|
||||
{
|
||||
int error;
|
||||
zfs_file_t *fp = zfs_file_get(fd);
|
||||
if (fp == NULL)
|
||||
return (NULL);
|
||||
|
||||
error = zfsdev_getminor(fd, minorp);
|
||||
int error = zfsdev_getminor(fp, minorp);
|
||||
if (error == 0)
|
||||
error = zfs_zevent_minor_to_state(*minorp, ze);
|
||||
|
||||
if (error)
|
||||
zfs_zevent_fd_rele(fd);
|
||||
if (error) {
|
||||
zfs_zevent_fd_rele(fp);
|
||||
fp = NULL;
|
||||
}
|
||||
|
||||
return (error);
|
||||
return (fp);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_zevent_fd_rele(int fd)
|
||||
zfs_zevent_fd_rele(zfs_file_t *fp)
|
||||
{
|
||||
zfs_file_put(fd);
|
||||
zfs_file_put(fp);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+34
-37
@@ -4861,8 +4861,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
|
||||
*errors = fnvlist_alloc();
|
||||
off = 0;
|
||||
|
||||
if ((error = zfs_file_get(input_fd, &input_fp)))
|
||||
return (error);
|
||||
if ((input_fp = zfs_file_get(input_fd)) == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
noff = off = zfs_file_off(input_fp);
|
||||
error = dmu_recv_begin(tofs, tosnap, begin_record, force,
|
||||
@@ -5142,7 +5142,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
|
||||
nvlist_free(inheritprops);
|
||||
}
|
||||
out:
|
||||
zfs_file_put(input_fd);
|
||||
zfs_file_put(input_fp);
|
||||
nvlist_free(origrecvd);
|
||||
nvlist_free(origprops);
|
||||
|
||||
@@ -5472,8 +5472,8 @@ zfs_ioc_send(zfs_cmd_t *zc)
|
||||
zfs_file_t *fp;
|
||||
dmu_send_outparams_t out = {0};
|
||||
|
||||
if ((error = zfs_file_get(zc->zc_cookie, &fp)))
|
||||
return (error);
|
||||
if ((fp = zfs_file_get(zc->zc_cookie)) == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
off = zfs_file_off(fp);
|
||||
out.dso_outfunc = dump_bytes;
|
||||
@@ -5483,7 +5483,7 @@ zfs_ioc_send(zfs_cmd_t *zc)
|
||||
zc->zc_fromobj, embedok, large_block_ok, compressok,
|
||||
rawok, savedok, zc->zc_cookie, &off, &out);
|
||||
|
||||
zfs_file_put(zc->zc_cookie);
|
||||
zfs_file_put(fp);
|
||||
}
|
||||
return (error);
|
||||
}
|
||||
@@ -6047,25 +6047,24 @@ zfs_ioc_tmp_snapshot(zfs_cmd_t *zc)
|
||||
{
|
||||
char *snap_name;
|
||||
char *hold_name;
|
||||
int error;
|
||||
minor_t minor;
|
||||
|
||||
error = zfs_onexit_fd_hold(zc->zc_cleanup_fd, &minor);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
zfs_file_t *fp = zfs_onexit_fd_hold(zc->zc_cleanup_fd, &minor);
|
||||
if (fp == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
snap_name = kmem_asprintf("%s-%016llx", zc->zc_value,
|
||||
(u_longlong_t)ddi_get_lbolt64());
|
||||
hold_name = kmem_asprintf("%%%s", zc->zc_value);
|
||||
|
||||
error = dsl_dataset_snapshot_tmp(zc->zc_name, snap_name, minor,
|
||||
int error = dsl_dataset_snapshot_tmp(zc->zc_name, snap_name, minor,
|
||||
hold_name);
|
||||
if (error == 0)
|
||||
(void) strlcpy(zc->zc_value, snap_name,
|
||||
sizeof (zc->zc_value));
|
||||
kmem_strfree(snap_name);
|
||||
kmem_strfree(hold_name);
|
||||
zfs_onexit_fd_rele(zc->zc_cleanup_fd);
|
||||
zfs_onexit_fd_rele(fp);
|
||||
return (error);
|
||||
}
|
||||
|
||||
@@ -6085,13 +6084,13 @@ zfs_ioc_diff(zfs_cmd_t *zc)
|
||||
offset_t off;
|
||||
int error;
|
||||
|
||||
if ((error = zfs_file_get(zc->zc_cookie, &fp)))
|
||||
return (error);
|
||||
if ((fp = zfs_file_get(zc->zc_cookie)) == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
off = zfs_file_off(fp);
|
||||
error = dmu_diff(zc->zc_name, zc->zc_value, fp, &off);
|
||||
|
||||
zfs_file_put(zc->zc_cookie);
|
||||
zfs_file_put(fp);
|
||||
|
||||
return (error);
|
||||
}
|
||||
@@ -6127,6 +6126,7 @@ zfs_ioc_hold(const char *pool, nvlist_t *args, nvlist_t *errlist)
|
||||
int cleanup_fd = -1;
|
||||
int error;
|
||||
minor_t minor = 0;
|
||||
zfs_file_t *fp = NULL;
|
||||
|
||||
holds = fnvlist_lookup_nvlist(args, "holds");
|
||||
|
||||
@@ -6144,14 +6144,16 @@ zfs_ioc_hold(const char *pool, nvlist_t *args, nvlist_t *errlist)
|
||||
}
|
||||
|
||||
if (nvlist_lookup_int32(args, "cleanup_fd", &cleanup_fd) == 0) {
|
||||
error = zfs_onexit_fd_hold(cleanup_fd, &minor);
|
||||
if (error != 0)
|
||||
return (SET_ERROR(error));
|
||||
fp = zfs_onexit_fd_hold(cleanup_fd, &minor);
|
||||
if (fp == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
}
|
||||
|
||||
error = dsl_dataset_user_hold(holds, minor, errlist);
|
||||
if (minor != 0)
|
||||
zfs_onexit_fd_rele(cleanup_fd);
|
||||
if (fp != NULL) {
|
||||
ASSERT3U(minor, !=, 0);
|
||||
zfs_onexit_fd_rele(fp);
|
||||
}
|
||||
return (SET_ERROR(error));
|
||||
}
|
||||
|
||||
@@ -6214,9 +6216,9 @@ zfs_ioc_events_next(zfs_cmd_t *zc)
|
||||
uint64_t dropped = 0;
|
||||
int error;
|
||||
|
||||
error = zfs_zevent_fd_hold(zc->zc_cleanup_fd, &minor, &ze);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
zfs_file_t *fp = zfs_zevent_fd_hold(zc->zc_cleanup_fd, &minor, &ze);
|
||||
if (fp == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
do {
|
||||
error = zfs_zevent_next(ze, &event,
|
||||
@@ -6238,7 +6240,7 @@ zfs_ioc_events_next(zfs_cmd_t *zc)
|
||||
break;
|
||||
} while (1);
|
||||
|
||||
zfs_zevent_fd_rele(zc->zc_cleanup_fd);
|
||||
zfs_zevent_fd_rele(fp);
|
||||
|
||||
return (error);
|
||||
}
|
||||
@@ -6270,12 +6272,12 @@ zfs_ioc_events_seek(zfs_cmd_t *zc)
|
||||
minor_t minor;
|
||||
int error;
|
||||
|
||||
error = zfs_zevent_fd_hold(zc->zc_cleanup_fd, &minor, &ze);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
zfs_file_t *fp = zfs_zevent_fd_hold(zc->zc_cleanup_fd, &minor, &ze);
|
||||
if (fp == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
error = zfs_zevent_seek(ze, zc->zc_guid);
|
||||
zfs_zevent_fd_rele(zc->zc_cleanup_fd);
|
||||
zfs_zevent_fd_rele(fp);
|
||||
|
||||
return (error);
|
||||
}
|
||||
@@ -6459,8 +6461,8 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
|
||||
|
||||
(void) nvlist_lookup_string(innvl, "redactbook", &redactbook);
|
||||
|
||||
if ((error = zfs_file_get(fd, &fp)))
|
||||
return (error);
|
||||
if ((fp = zfs_file_get(fd)) == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
off = zfs_file_off(fp);
|
||||
|
||||
@@ -6472,7 +6474,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
|
||||
compressok, rawok, savedok, resumeobj, resumeoff,
|
||||
redactbook, fd, &off, &out);
|
||||
|
||||
zfs_file_put(fd);
|
||||
zfs_file_put(fp);
|
||||
return (error);
|
||||
}
|
||||
|
||||
@@ -7345,17 +7347,12 @@ pool_status_check(const char *name, zfs_ioc_namecheck_t type,
|
||||
}
|
||||
|
||||
int
|
||||
zfsdev_getminor(int fd, minor_t *minorp)
|
||||
zfsdev_getminor(zfs_file_t *fp, minor_t *minorp)
|
||||
{
|
||||
zfsdev_state_t *zs, *fpd;
|
||||
zfs_file_t *fp;
|
||||
int rc;
|
||||
|
||||
ASSERT(!MUTEX_HELD(&zfsdev_state_lock));
|
||||
|
||||
if ((rc = zfs_file_get(fd, &fp)))
|
||||
return (rc);
|
||||
|
||||
fpd = zfs_file_private(fp);
|
||||
if (fpd == NULL)
|
||||
return (SET_ERROR(EBADF));
|
||||
|
||||
+13
-10
@@ -107,30 +107,33 @@ zfs_onexit_destroy(zfs_onexit_t *zo)
|
||||
* of this function must call zfs_onexit_fd_rele() when they're finished
|
||||
* using the minor number.
|
||||
*/
|
||||
int
|
||||
zfs_file_t *
|
||||
zfs_onexit_fd_hold(int fd, minor_t *minorp)
|
||||
{
|
||||
zfs_onexit_t *zo = NULL;
|
||||
int error;
|
||||
|
||||
error = zfsdev_getminor(fd, minorp);
|
||||
zfs_file_t *fp = zfs_file_get(fd);
|
||||
if (fp == NULL)
|
||||
return (NULL);
|
||||
|
||||
int error = zfsdev_getminor(fp, minorp);
|
||||
if (error) {
|
||||
zfs_onexit_fd_rele(fd);
|
||||
return (error);
|
||||
zfs_onexit_fd_rele(fp);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
zo = zfsdev_get_state(*minorp, ZST_ONEXIT);
|
||||
if (zo == NULL) {
|
||||
zfs_onexit_fd_rele(fd);
|
||||
return (SET_ERROR(EBADF));
|
||||
zfs_onexit_fd_rele(fp);
|
||||
return (NULL);
|
||||
}
|
||||
return (0);
|
||||
return (fp);
|
||||
}
|
||||
|
||||
void
|
||||
zfs_onexit_fd_rele(int fd)
|
||||
zfs_onexit_fd_rele(zfs_file_t *fp)
|
||||
{
|
||||
zfs_file_put(fd);
|
||||
zfs_file_put(fp);
|
||||
}
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user