Skip to content

Commit

Permalink
Various fixes for snapshot mounts
Browse files Browse the repository at this point in the history
Snapshot directory listing (.zfs and .zfs/dataset) missed
checking for SINGLE_ENTRY flags, confusing DOS.

FSCTL_GET_REPARSE_POINT should not return "needed bytes" in
IoStatus.Information. Or rather, STATUS_BUFFER_OVERFLOW does
not return "needed bytes", but rather amount that fit, often 0.
STATUS_BUFFER_TOO_SMALL returns "needed bytes". This caused
Explorer.exe to stack overflow.

Stop ctldir nodes (.zfs, .zfs/snapshot and .zfs/snapshot/*)
from being reclaimed (usecount++).

Make sure to attach security to ctldir entries.

Signed-off-by: Jorgen Lundman <[email protected]>
  • Loading branch information
lundman committed Sep 25, 2024
1 parent 6fda747 commit 5fb69ad
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 65 deletions.
3 changes: 2 additions & 1 deletion include/os/windows/zfs/sys/zfs_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ extern char *create_options(ULONG options);
extern char *create_reply(NTSTATUS, ULONG reply);
extern void latency_stats(uint64_t *histo, unsigned int buckets,
stat_pair *lat);
extern size_t get_reparse_point_impl(znode_t *zp, char *buffer, size_t outlen);
extern int get_reparse_point_impl(znode_t *zp, char *buffer, size_t bufferlen,
size_t *retlen);
extern void fastio_init(FAST_IO_DISPATCH **fast);
extern NTSTATUS pnp_query_di(PDEVICE_OBJECT DeviceObject, PIRP Irp,
PIO_STACK_LOCATION IrpSp);
Expand Down
3 changes: 2 additions & 1 deletion module/os/windows/spl/spl-vnode.c
Original file line number Diff line number Diff line change
Expand Up @@ -2194,8 +2194,9 @@ vnode_clear_easize(struct vnode *vp)
void
vnode_set_reparse(struct vnode *vp, REPARSE_DATA_BUFFER *rpp, size_t size)
{
if (vp->v_reparse != NULL && size > 0)
if (vp->v_reparse != NULL && size > 0) {
kmem_free(vp->v_reparse, vp->v_reparse_size);
}
vp->v_reparse = NULL;
vp->v_reparse_size = 0;

Expand Down
43 changes: 32 additions & 11 deletions module/os/windows/zfs/zfs_ctldir.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ zfsctl_vnode_alloc(zfsvfs_t *zfsvfs, uint64_t id,
zp->z_zn_prefetch = B_FALSE;
zp->z_is_sa = B_FALSE;
zp->z_is_mapped = B_FALSE;
zp->z_is_ctldir = B_TRUE;
zp->z_is_ctldir = B_TRUE; // xxx
zp->z_sa_hdl = NULL;
zp->z_blksz = 0;
zp->z_seq = 0;
Expand Down Expand Up @@ -269,13 +269,17 @@ zfsctl_vnode_alloc(zfsvfs_t *zfsvfs, uint64_t id,
if (id < zfsvfs->z_ctldir_startid)
zfsvfs->z_ctldir_startid = id;

zfs_attach_security(vp, NULL);
zfs_attach_security(vp, parentvp);

mutex_enter(&zfsvfs->z_znodes_lock);
list_insert_tail(&zfsvfs->z_all_znodes, zp);
membar_producer();
mutex_exit(&zfsvfs->z_znodes_lock);

// We can't have the fake ctldir vnodes being reclaimed,
// so we hold them until ctldir_destroy() is called
vnode_ref(vp);

return (vp);
}

Expand All @@ -300,7 +304,6 @@ zfsctl_vnode_lookup(zfsvfs_t *zfsvfs, uint64_t id,
/* May fail due to concurrent zfsctl_vnode_alloc() */
ip = zfsctl_vnode_alloc(zfsvfs, id, name);
}

return (ip);
}

Expand Down Expand Up @@ -358,10 +361,11 @@ zfsctl_destroy(zfsvfs_t *zfsvfs)
* Add a hold to the vnode and return it.
*/
struct vnode *
zfsctl_root(znode_t *zp)
zfsctl_root(znode_t *rzp)
{
VN_HOLD(ZTOZSB(zp)->z_ctldir);
return (ZTOZSB(zp)->z_ctldir);
vnode_t *vp = ZTOZSB(rzp)->z_ctldir;
VN_HOLD(vp);
return (vp);
}

/* Given a entry in .zfs, find its parent */
Expand Down Expand Up @@ -441,6 +445,7 @@ zfsctl_vnop_readdir_root(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
int error = 0;
znode_t *zp = VTOZ(vp);
zfsvfs_t *zfsvfs = zp->z_zfsvfs;
int flag_return_single_entry = flags & SL_RETURN_SINGLE_ENTRY ? 1 : 0;

dprintf("%s\n", __func__);

Expand Down Expand Up @@ -478,6 +483,7 @@ zfsctl_vnop_readdir_root(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
}

ctx->offset++;
if (flag_return_single_entry) break;
}

if (error == ENOENT) {
Expand Down Expand Up @@ -508,9 +514,11 @@ zfsctl_vnop_readdir_snapdir(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
int error = 0;
boolean_t case_conflict;
uint64_t id;
uint64_t pos = 0;
char snapname[MAXNAMELEN];
znode_t *zp = VTOZ(vp);
zfsvfs_t *zfsvfs = zp->z_zfsvfs;
int flag_return_single_entry = flags & SL_RETURN_SINGLE_ENTRY ? 1 : 0;

dprintf("%s\n", __func__);

Expand All @@ -523,18 +531,20 @@ zfsctl_vnop_readdir_snapdir(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
case 0: /* "." */
error = zfs_readdir_emitdir(zfsvfs, ".",
ctx, zccb, ZFSCTL_INO_SNAPDIR);
ctx->offset++;
break;

case 1: /* ".." */
error = zfs_readdir_emitdir(zfsvfs, "..",
ctx, zccb, ZFSCTL_INO_ROOT);
ctx->offset++;
break;

default:
dsl_pool_config_enter(dmu_objset_pool(zfsvfs->z_os),
FTAG);
error = dmu_snapshot_list_next(zfsvfs->z_os,
MAXNAMELEN, snapname, &id, &ctx->offset,
MAXNAMELEN, snapname, &id, &pos,
&case_conflict);
dsl_pool_config_exit(dmu_objset_pool(zfsvfs->z_os),
FTAG);
Expand All @@ -543,17 +553,21 @@ zfsctl_vnop_readdir_snapdir(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,

error = zfs_readdir_emitdir(zfsvfs, snapname,
ctx, zccb, ZFSCTL_INO_SNAPDIRS - id);
ctx->offset++;

break;
}

if (error != 0) {
dprintf("emit error\n");
break;
}

ctx->offset++;
if (flag_return_single_entry) break;
}

if (pos != 0)
ctx->offset = pos;

zfs_readdir_complete(ctx);

zfs_exit(zfsvfs, FTAG);
Expand All @@ -570,6 +584,7 @@ zfsctl_vnop_readdir_snapdirs(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
int error = 0;
znode_t *zp = VTOZ(vp);
zfsvfs_t *zfsvfs = zp->z_zfsvfs;
int flag_return_single_entry = flags & SL_RETURN_SINGLE_ENTRY ? 1 : 0;

if ((error = zfs_enter(zfsvfs, FTAG)) != 0)
return (error);
Expand Down Expand Up @@ -598,6 +613,8 @@ zfsctl_vnop_readdir_snapdirs(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
}

ctx->offset++;

if (flag_return_single_entry) break;
}

zfs_exit(zfsvfs, FTAG);
Expand Down Expand Up @@ -626,7 +643,6 @@ zfsctl_readdir(vnode_t *vp, emitdir_ptr_t *ctx, cred_t *cr,
default:
return (zfsctl_vnop_readdir_snapdirs(vp, ctx, cr,
zccb, flags));
break;
}
panic("%s: weird snapshot state\n", __func__);
return (EINVAL);
Expand Down Expand Up @@ -1185,8 +1201,13 @@ zfsctl_set_reparse_point(znode_t *zp, REPARSE_DATA_BUFFER *rdb, size_t size)
if (!zfsctl_is_leafnode(zp))
return (STATUS_INVALID_PARAMETER);

vnode_t *vp = ZTOV(zp);
vnode_t *dvp = NULL;
dvp = zfsctl_vnode_lookup(zp->z_zfsvfs, ZFSCTL_INO_SNAPDIR,
ZFS_SNAPDIR_NAME);
zp->z_pflags |= ZFS_REPARSE;
vnode_set_reparse(ZTOV(zp), rdb, size);
vnode_set_reparse(vp, rdb, size);
VN_RELE(dvp);
return (STATUS_SUCCESS);
}

Expand Down
2 changes: 1 addition & 1 deletion module/os/windows/zfs/zfs_vfsops.c
Original file line number Diff line number Diff line change
Expand Up @@ -1798,7 +1798,7 @@ zfs_vfs_vget(struct mount *mp, ino64_t ino, vnode_t **vpp,
mutex_exit(&zfsvfs->z_znodes_lock);

error = ENOENT;
if (zp != NULL) {
if (zp != NULL && ZTOV(zp) != NULL) {
if (VN_HOLD(ZTOV(zp)) == 0) {
*vpp = ZTOV(zp);
error = 0;
Expand Down
59 changes: 31 additions & 28 deletions module/os/windows/zfs/zfs_vnops_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ allocate_reparse(struct vnode *vp, char *finalname, PIRP Irp)
zp->z_size;
rpb = ExAllocatePoolWithTag(PagedPool,
size, '!FSZ');
get_reparse_point_impl(zp, (char *)rpb, size);
get_reparse_point_impl(zp, (char *)rpb, size, NULL);

/*
* Length, in bytes, of the unparsed portion of the
Expand Down Expand Up @@ -1375,7 +1375,6 @@ zfs_vnop_lookup_impl(PIRP Irp, PIO_STACK_LOCATION IrpSp, mount_t *zmo,
xoap->xoa_case_sensitive_dir = 1;
XVA_SET_REQ(xvap, XAT_CASESENSITIVEDIR);
}

ASSERT(strchr(finalname, '\\') == NULL);
error = zfs_mkdir(VTOZ(dvp), finalname, vap, &zp, NULL,
flags, NULL, NULL);
Expand Down Expand Up @@ -1496,6 +1495,7 @@ zfs_vnop_lookup_impl(PIRP Irp, PIO_STACK_LOCATION IrpSp, mount_t *zmo,
if (DeleteOnClose &&
vp && zp &&
dvp && VTOZ(dvp) &&
!zfsctl_is_node(VTOZ(dvp)) &&
zfs_zaccess_delete(VTOZ(dvp), zp, 0, NULL) > 0) {
VN_RELE(vp);
if (dvp)
Expand Down Expand Up @@ -3147,34 +3147,41 @@ set_ea(PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
return (Status);
}

size_t
get_reparse_point_impl(znode_t *zp, char *buffer, size_t outlen)
int
get_reparse_point_impl(znode_t *zp, char *buffer, size_t bufferlen,
size_t *returnlen)
{
size_t size = 0;
int err = 0;
if (zp->z_pflags & ZFS_REPARSE) {
int err;

// Return the needed total size, but only copy as
// much as we can fit.
// WEIRDLY, Explorer will crash if we return
// neededbytes in Information. It should be 0.
if (zfsctl_is_node(zp)) {
REPARSE_DATA_BUFFER *rdb = NULL;
NTSTATUS Status;
size_t size = 0;

Status = zfsctl_get_reparse_point(zp, &rdb, &size);
if (Status == 0)
if (Status == 0 && bufferlen >= size)
memcpy(buffer, rdb, size);
if (returnlen)
*returnlen = 0; // size
} else {
size = MIN(zp->z_size, outlen);
struct iovec iov;
iov.iov_base = (void *)buffer;
iov.iov_len = size;
iov.iov_len = MIN(zp->z_size, bufferlen);

zfs_uio_t uio;
zfs_uio_iovec_init(&uio, &iov, 1, 0, UIO_SYSSPACE,
size, 0);
iov.iov_len, 0);
err = zfs_readlink(ZTOV(zp), &uio, NULL);
if (err)
size = 0;
if (!err && returnlen)
*returnlen = zp->z_size - zfs_uio_resid(&uio);
}
}
return (size);
return (err);
}

NTSTATUS
Expand All @@ -3196,7 +3203,6 @@ get_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp,
if (vp == NULL)
return (STATUS_INVALID_PARAMETER);

VN_HOLD(vp);
znode_t *zp = VTOZ(vp);

if (vnode_islnk(vp)) {
Expand All @@ -3223,17 +3229,21 @@ get_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp,
int err;
size_t size = 0;

size = get_reparse_point_impl(zp, buffer, outlen);
err = get_reparse_point_impl(zp, buffer, outlen, &size);
Irp->IoStatus.Information = size;

if (err)
Status = STATUS_UNEXPECTED_IO_ERROR;

if (outlen < size)
Status = STATUS_BUFFER_OVERFLOW;
else
Status = STATUS_SUCCESS;
}

}

end:
VN_RELE(vp);

dprintf("%s: returning 0x%lx\n", __func__, Status);
return (Status);
}
Expand Down Expand Up @@ -3282,19 +3292,12 @@ set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp,
if (zfsvfs->z_rdonly)
return (STATUS_MEDIA_WRITE_PROTECTED);

VN_HOLD(vp);
znode_t *dzp = NULL;
uint64_t parent;
int error;

// Fetch parent
VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs),
&parent, sizeof (parent)) == 0);
error = zfs_zget(zfsvfs, parent, &dzp);
if (error) {
Status = STATUS_INVALID_PARAMETER;
goto out;
}
vnode_t *dvp = NULL;
dvp = zfs_parent(vp);
dzp = VTOZ(dvp);

// winbtrfs' test/exe will trigger this, add code here.
// (asked to create reparse point on already reparse point)
Expand Down Expand Up @@ -3355,7 +3358,7 @@ set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp,
out:
if (dzp)
zrele(dzp);
VN_RELE(vp);
VN_RELE(dvp);

dprintf("%s: returning 0x%lx\n", __func__, Status);

Expand Down
Loading

0 comments on commit 5fb69ad

Please sign in to comment.