unionfs: work around underlying FS failing to respect cn_namelen

unionfs_mkshadowdir() may be invoked on a non-leaf pathname component
during lookup, in which case the NUL terminator of the pathname buffer
will be well beyond the end of the current component.  cn_namelen in
this case will still (correctly) indicate the length of only the
current component, but ZFS in particular does not currently respect
cn_namelen, leading to the creation on inacessible files with slashes
in their names.  Work around this behavior by temporarily NUL-
terminating the current pathname component for the call to VOP_MKDIR().

https://github.com/openzfs/zfs/issues/15705 has been filed to track
a proper upstream fix for the issue at hand.

PR:		275871
Reported by:	Karlo Miličević <karlo98.m@gmail.com>
Tested by:	Karlo Miličević <karlo98.m@gmail.com>
Reviewed by:	kib, olce
MFC after:	2 weeks
Differential Revision: https://reviews.freebsd.org/D43818
This commit is contained in:
Jason A. Harmening 2023-12-23 22:48:19 -06:00
parent 2656fc29be
commit a2ddbe019d

View file

@ -916,7 +916,24 @@ unionfs_mkshadowdir(struct unionfs_mount *ump, struct vnode *udvp,
goto unionfs_mkshadowdir_abort;
unionfs_create_uppervattr_core(ump, &lva, &va, td);
/*
* Temporarily NUL-terminate the current pathname component.
* This function may be called during lookup operations in which
* the current pathname component is not the leaf, meaning that
* the NUL terminator is some distance beyond the end of the current
* component. This *should* be fine, as cn_namelen will still
* correctly indicate the length of only the current component,
* but ZFS in particular does not respect cn_namelen in its VOP_MKDIR
* implementation
* Note that this assumes nd.ni_cnd.cn_pnbuf was allocated by
* something like a local namei() operation and the temporary
* NUL-termination will not have an effect on other threads.
*/
char *pathend = &nd.ni_cnd.cn_nameptr[nd.ni_cnd.cn_namelen];
char pathterm = *pathend;
*pathend = '\0';
error = VOP_MKDIR(udvp, &uvp, &nd.ni_cnd, &va);
*pathend = pathterm;
if (!error) {
unionfs_node_update(unp, uvp, td);