3030#include < libkern/c++/OSIterator.h>
3131#include < libkern/c++/OSBoolean.h>
3232#include < libkern/c++/OSString.h>
33+ #include < libkern/c++/OSSymbol.h>
34+ #include < libkern/c++/OSNumber.h>
35+ #include < libkern/c++/OSArray.h>
36+ #include < libkern/c++/OSDictionary.h>
37+ #include < libkern/c++/OSCollectionIterator.h>
38+
39+ /*
40+ * OSKext::copyLoadedKextInfo() is declared here rather than by including
41+ * <libkern/c++/OSKext.h>, which is unusable from a kext build: it pulls in
42+ * <libkern/OSKextLibPrivate.h>, a private header the SDK does not ship. A
43+ * one-method declaration is enough - a static member call mangles from the
44+ * class name and parameter types alone, so this emits exactly the same symbol
45+ * reference the real header would, without vendoring private headers.
46+ *
47+ * The return type is spelled OSDictionary* because OSPtr<T> is a plain T* in
48+ * kext builds, and the return type takes no part in C++ mangling regardless.
49+ *
50+ * Caller owns a reference to the returned dictionary.
51+ */
52+ class OSKext {
53+ public:
54+ static OSDictionary *copyLoadedKextInfo (OSArray *kextIdentifiers,
55+ OSArray *keys);
56+ };
3357#include < libkern/libkern.h>
3458
3559#include < string.h>
@@ -47,6 +71,12 @@ extern "C" {
4771#define SYSFS_IOKIT_BSDNAMEMAX 32
4872/* Sanity cap on service-plane entries in one snapshot. */
4973#define SYSFS_IOKIT_MAXNODES 16384u
74+
75+ /*
76+ * Upper bound on loaded kexts recorded for /sys/module. A running system has a
77+ * few hundred; the cap only stops a pathological case from unbounded growth.
78+ */
79+ #define SYSFS_IOKIT_MAXMODULES 1024u
5080/* How long a snapshot is reused before a rebuild (ns). */
5181#define SYSFS_SNAP_TTL_NS 2000000000ULL /* 2 seconds */
5282
@@ -105,7 +135,30 @@ struct sysfs_hash_slot {
105135};
106136
107137/* Container holding a full self-contained IOKit registry snapshot */
138+ /*
139+ * One loaded kernel extension, as presented under /sys/module/<name>.
140+ *
141+ * Captured on the refresh thread with the rest of the snapshot, never in a
142+ * vnop: OSKext::copyLoadedKextInfo() takes the kext lock and builds a
143+ * dictionary describing every loaded kext, which is far too heavy - and too
144+ * lock-entangled - to do while holding VFS locks.
145+ *
146+ * load_tag is the kext's OSBundleLoadTag: unique among loaded kexts and stable
147+ * for as long as the kext stays loaded, which makes it the natural per-node key
148+ * (the same role IORegistryEntryID plays for /sys/devices).
149+ */
150+ struct sysfs_snap_module {
151+ uint64_t load_tag;
152+ uint64_t load_size; /* OSBundleLoadSize, bytes */
153+ uint64_t refcnt; /* OSBundleRetainCount */
154+ char name[128 ]; /* CFBundleIdentifier */
155+ char version[32 ]; /* CFBundleVersion */
156+ };
157+
108158struct sysfs_snapshot {
159+ struct sysfs_snap_module *modules;
160+ uint32_t module_count;
161+ uint32_t module_cap;
109162 struct sysfs_snap_node *nodes;
110163 uint32_t *child_idx;
111164 struct sysfs_hash_slot *hash;
@@ -217,6 +270,10 @@ sysfs_snap_free(struct sysfs_snapshot *snap)
217270 if (snap == nullptr ) {
218271 return ;
219272 }
273+ if (snap->modules != nullptr ) {
274+ sysfs_free (snap->modules ,
275+ (size_t )snap->module_cap * sizeof (*snap->modules ));
276+ }
220277 if (snap->nodes != nullptr ) {
221278 sysfs_free (snap->nodes , (size_t )snap->node_cap * sizeof (*snap->nodes ));
222279 }
@@ -300,6 +357,109 @@ sysfs_count_entries(void)
300357}
301358
302359
360+
361+ /*
362+ * Capture the loaded kext list into the snapshot, for /sys/module.
363+ *
364+ * copyLoadedKextInfo() is asked for only the four keys we publish rather than
365+ * the full per-kext dictionary, which keeps the work (and the time the kext
366+ * lock is held) proportional to what we actually use. A kext missing any of
367+ * them is still listed - the value simply reads as zero or empty - because a
368+ * present-but-incomplete module is more useful than a hidden one.
369+ *
370+ * Failure here is never fatal: the snapshot is published regardless and
371+ * /sys/module reads as empty, exactly as it does before the first refresh.
372+ */
373+ static void
374+ sysfs_snap_build_modules (struct sysfs_snapshot *snap)
375+ {
376+ static const char * const wanted[] = {
377+ " OSBundleLoadTag" ,
378+ " OSBundleLoadSize" ,
379+ " OSBundleRetainCount" ,
380+ " CFBundleVersion" ,
381+ };
382+
383+ OSArray *keys = OSArray::withCapacity (4 );
384+ if (keys == nullptr ) {
385+ return ;
386+ }
387+ for (unsigned i = 0 ; i < 4 ; i++) {
388+ const OSSymbol *sym = OSSymbol::withCString (wanted[i]);
389+ if (sym != nullptr ) {
390+ keys->setObject (const_cast <OSSymbol *>(sym));
391+ sym->release ();
392+ }
393+ }
394+
395+ OSDictionary *info = OSKext::copyLoadedKextInfo (nullptr , keys);
396+ keys->release ();
397+ if (info == nullptr ) {
398+ return ;
399+ }
400+
401+ uint32_t cap = info->getCount ();
402+ if (cap == 0 ) {
403+ info->release ();
404+ return ;
405+ }
406+ if (cap > SYSFS_IOKIT_MAXMODULES ) {
407+ cap = SYSFS_IOKIT_MAXMODULES ;
408+ }
409+
410+ size_t bytes = (size_t )cap * sizeof (struct sysfs_snap_module );
411+ struct sysfs_snap_module *arr =
412+ (struct sysfs_snap_module *)sysfs_alloc (bytes);
413+ if (arr == nullptr ) {
414+ info->release ();
415+ return ;
416+ }
417+
418+ OSCollectionIterator *it = OSCollectionIterator::withCollection (info);
419+ if (it == nullptr ) {
420+ sysfs_free (arr, bytes);
421+ info->release ();
422+ return ;
423+ }
424+
425+ uint32_t n = 0 ;
426+ OSObject *k;
427+ while ((k = it->getNextObject ()) != nullptr && n < cap) {
428+ OSString *ident = OSDynamicCast (OSString, k);
429+ if (ident == nullptr ) {
430+ continue ;
431+ }
432+ OSDictionary *kd = OSDynamicCast (OSDictionary, info->getObject (ident));
433+ if (kd == nullptr ) {
434+ continue ;
435+ }
436+
437+ struct sysfs_snap_module *m = &arr[n];
438+ strlcpy (m->name , ident->getCStringNoCopy (), sizeof (m->name ));
439+
440+ OSNumber *num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleLoadTag" ));
441+ m->load_tag = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
442+ num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleLoadSize" ));
443+ m->load_size = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
444+ num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleRetainCount" ));
445+ m->refcnt = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
446+
447+ OSString *ver = OSDynamicCast (OSString, kd->getObject (" CFBundleVersion" ));
448+ if (ver != nullptr ) {
449+ strlcpy (m->version , ver->getCStringNoCopy (), sizeof (m->version ));
450+ }
451+ n++;
452+ }
453+ it->release ();
454+ info->release ();
455+
456+ snap->modules = arr;
457+ snap->module_cap = cap;
458+ snap->module_count = n;
459+ snap->bytes += bytes;
460+ OSAddAtomic64 ((int64_t )bytes, &sysfs_stat_snap_bytes);
461+ }
462+
303463/* Builds a fresh snapshot completely in isolation without holding global locks. */
304464static struct sysfs_snapshot *
305465sysfs_snap_build (void )
@@ -534,6 +694,8 @@ sysfs_snap_build(void)
534694 }
535695 }
536696
697+ sysfs_snap_build_modules (snap);
698+
537699 clock_get_uptime (&snap->uptime );
538700 return snap;
539701}
@@ -984,3 +1146,83 @@ sysfs_iokit_name(uint64_t regid, char *buf, size_t buflen)
9841146 IOLockUnlock (g_lock);
9851147 return n;
9861148}
1149+
1150+ #pragma mark - loaded kernel extensions (/sys/module)
1151+
1152+ /*
1153+ * Copy one snapshot module record out to the caller. Must hold g_lock.
1154+ */
1155+ static void
1156+ sysfs_module_copy_out (const struct sysfs_snap_module *m,
1157+ struct sysfs_module_info *out)
1158+ {
1159+ out->load_tag = m->load_tag ;
1160+ out->load_size = m->load_size ;
1161+ out->refcnt = m->refcnt ;
1162+ strlcpy (out->name , m->name , sizeof (out->name ));
1163+ strlcpy (out->version , m->version , sizeof (out->version ));
1164+ }
1165+
1166+ extern " C" int
1167+ sysfs_iokit_module_at (unsigned int index, struct sysfs_module_info *out)
1168+ {
1169+ if (out == nullptr || g_lock == nullptr ) {
1170+ return 0 ;
1171+ }
1172+ sysfs_snap_ensure ();
1173+ IOLockLock (g_lock);
1174+ int ok = 0 ;
1175+ if (g_snapshot != nullptr && g_snapshot->modules != nullptr &&
1176+ index < g_snapshot->module_count ) {
1177+ sysfs_module_copy_out (&g_snapshot->modules [index], out);
1178+ ok = 1 ;
1179+ }
1180+ IOLockUnlock (g_lock);
1181+ return ok;
1182+ }
1183+
1184+ extern " C" int
1185+ sysfs_iokit_module_named (const char *name, size_t namelen,
1186+ struct sysfs_module_info *out)
1187+ {
1188+ if (name == nullptr || out == nullptr || g_lock == nullptr ) {
1189+ return 0 ;
1190+ }
1191+ sysfs_snap_ensure ();
1192+ IOLockLock (g_lock);
1193+ int ok = 0 ;
1194+ if (g_snapshot != nullptr && g_snapshot->modules != nullptr ) {
1195+ for (uint32_t i = 0 ; i < g_snapshot->module_count ; i++) {
1196+ const char *nm = g_snapshot->modules [i].name ;
1197+ if (strlen (nm) == namelen && strncmp (nm, name, namelen) == 0 ) {
1198+ sysfs_module_copy_out (&g_snapshot->modules [i], out);
1199+ ok = 1 ;
1200+ break ;
1201+ }
1202+ }
1203+ }
1204+ IOLockUnlock (g_lock);
1205+ return ok;
1206+ }
1207+
1208+ extern " C" int
1209+ sysfs_iokit_module_by_tag (uint64_t load_tag, struct sysfs_module_info *out)
1210+ {
1211+ if (out == nullptr || g_lock == nullptr ) {
1212+ return 0 ;
1213+ }
1214+ sysfs_snap_ensure ();
1215+ IOLockLock (g_lock);
1216+ int ok = 0 ;
1217+ if (g_snapshot != nullptr && g_snapshot->modules != nullptr ) {
1218+ for (uint32_t i = 0 ; i < g_snapshot->module_count ; i++) {
1219+ if (g_snapshot->modules [i].load_tag == load_tag) {
1220+ sysfs_module_copy_out (&g_snapshot->modules [i], out);
1221+ ok = 1 ;
1222+ break ;
1223+ }
1224+ }
1225+ }
1226+ IOLockUnlock (g_lock);
1227+ return ok;
1228+ }
0 commit comments