Skip to content

Commit c0cd6e3

Browse files
fix: kext: sysfs_vnops: fail unimplemented vnops instead of faking success
sysfs_vnop_default() returned 0 - "handled, successfully" - for every vnode operation this filesystem does not implement. The caller then read back output parameters we never wrote: getxattr and listxattr appeared to succeed with an uninitialised result length, pathconf yielded an uninitialised limit, and pagein claimed to have filled a page it never faulted in. Finder, the Dock, LaunchServices and the pasteboard query exactly those on paths they enumerate, so the damage presented as a system fault rather than a filesystem one: apps progressively failing to launch, copy/paste hanging, the Dock going unresponsive, recoverable only by rebooting. Crucially the volume needed is tiny - a handful of poisoned replies is enough to wedge a process permanently - which is why the filesystem looked idle throughout (one live vnode, one snapshot build) while the machine degraded. Return ENOTSUP, which is what VFS expects from an unsupported optional operation and which it handles gracefully everywhere. Implement vnop_pathconf properly as well, since callers size buffers and bound loops from its result.
1 parent d157e6a commit c0cd6e3

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

kext/sysfs_vnops.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ STATIC int sysfs_vnop_open(struct vnop_open_args *ap);
116116
STATIC int sysfs_vnop_close(struct vnop_close_args *ap);
117117
STATIC int sysfs_vnop_access(struct vnop_access_args *ap);
118118
STATIC int sysfs_vnop_inactive(struct vnop_inactive_args *ap);
119+
STATIC int sysfs_vnop_pathconf(struct vnop_pathconf_args *ap);
119120

120121
STATIC inline int sysfs_calc_dirent_size(const char *name);
121122
STATIC int sysfs_copyout_dirent(int type, uint64_t file_id, const char *name, uio_t uio, int *sizep, off_t seekoff);
@@ -154,6 +155,7 @@ struct vnodeopv_entry_desc sysfs_vnodeop_entries[] = {
154155
{ .opve_op = &vnop_readdirattr_desc, .opve_impl = (VOPFUNC) err_readdirattr }, /* readdirattr -> ENOTSUP, forces fallback to getdirentries64 */
155156
{ .opve_op = &vnop_getattrlistbulk_desc, .opve_impl = (VOPFUNC) sysfs_vnop_getattrlistbulk }, /* getattrlistbulk -> ENOTSUP, forces fallback to readdir+getattr */
156157
{ .opve_op = &vnop_readlink_desc, .opve_impl = (VOPFUNC) sysfs_vnop_readlink }, /* readlink */
158+
{ .opve_op = &vnop_pathconf_desc, .opve_impl = (VOPFUNC) sysfs_vnop_pathconf }, /* pathconf */
157159
{ .opve_op = &vnop_inactive_desc, .opve_impl = (VOPFUNC) sysfs_vnop_inactive }, /* inactive */
158160
{ .opve_op = &vnop_reclaim_desc, .opve_impl = (VOPFUNC) sysfs_vnop_reclaim }, /* reclaim */
159161
{ .opve_op = (struct vnodeop_desc*)NULL, .opve_impl = (int (*)(void *))NULL }
@@ -210,9 +212,61 @@ sysfs_vnop_inactive(__unused struct vnop_inactive_args *ap)
210212
return 0;
211213
}
212214

215+
/*
216+
* Fallback for every vnode operation this filesystem does not implement.
217+
*
218+
* This MUST report failure. Returning 0 tells VFS "handled, successfully" for
219+
* operations we never touched - so the caller reads back output parameters we
220+
* never wrote. That is not theoretical: getxattr/listxattr appear to succeed
221+
* with an uninitialised result length, pathconf yields an uninitialised limit,
222+
* and pagein claims to have filled a page it never faulted in. Finder, the Dock
223+
* and LaunchServices query exactly those on paths they enumerate, which makes
224+
* the damage look like a system problem rather than a filesystem one: launches
225+
* and pasteboard operations wedge, and processes that consumed a bogus value
226+
* never recover.
227+
*
228+
* ENOTSUP is what VFS expects from an unsupported optional operation, and it
229+
* handles it gracefully everywhere.
230+
*/
213231
STATIC int
214232
sysfs_vnop_default(__unused struct vnop_generic_args *arg)
215233
{
234+
return ENOTSUP;
235+
}
236+
237+
/*
238+
* Path limits. Implemented explicitly because the default above now (correctly)
239+
* fails, and because a caller that gets a garbage limit back can size a buffer
240+
* or bound a loop from it.
241+
*/
242+
STATIC int
243+
sysfs_vnop_pathconf(struct vnop_pathconf_args *ap)
244+
{
245+
switch (ap->a_name) {
246+
case _PC_LINK_MAX:
247+
*ap->a_retval = 1; /* no hard links */
248+
break;
249+
case _PC_NAME_MAX:
250+
*ap->a_retval = NAME_MAX;
251+
break;
252+
case _PC_PATH_MAX:
253+
*ap->a_retval = PATH_MAX;
254+
break;
255+
case _PC_CHOWN_RESTRICTED:
256+
*ap->a_retval = 200112; /* _POSIX_CHOWN_RESTRICTED */
257+
break;
258+
case _PC_NO_TRUNC:
259+
*ap->a_retval = 0; /* long names are an error, not truncated */
260+
break;
261+
case _PC_CASE_SENSITIVE:
262+
*ap->a_retval = 1;
263+
break;
264+
case _PC_CASE_PRESERVING:
265+
*ap->a_retval = 1;
266+
break;
267+
default:
268+
return EINVAL;
269+
}
216270
return 0;
217271
}
218272

0 commit comments

Comments
 (0)