I reported this as a bug (FB18614667), but also wanted to ask here in case this is actually just me doing something wrong, or maybe I'm misunderstanding the entire use case of metadataRead. (My understanding is that metadataRead is basically read but it checks a cache that the kernel manages before trying to read the physical resource, and in the case of a cache miss it would just go to the physical resource and then add the bytes to the cache. Is that right?)
I’m encountering an issue in an FSKit file system extension where (for example)
read(into: buf, startingAt: 0, length: Int(physicalBlockSize))
works, but
metadataRead(into: buf, startingAt: 0, length: Int(physicalBlockSize))
throws an EIO error (Input/output error) no matter what I do.
(Note: physicalBlockSize is 512 in this example.)
The documentation (https://developer.apple.com/documentation/fskit/fsblockdeviceresource/metadataread(into:startingat:length:)) indicates that the restrictions on metadataRead are that the operations must be sector-addressed (which is the case here, especially as regular read has the same restriction and succeeds) and that partial reading of metadata is not supported. (I don’t think that applies here?)
In a sample project I was able to replicate this behavior where the module only ever reads the block device in its enumerateDirectory implementation, and so trying to list the contents of a directory leads to an "Input/output error" when e.g. running ls on the volume.
The enumerateDirectory sample implementation is like so:
func enumerateDirectory(_ directory: FSItem, startingAt cookie: FSDirectoryCookie, verifier: FSDirectoryVerifier, attributes: FSItem.GetAttributesRequest?, packer: FSDirectoryEntryPacker) async throws -> FSDirectoryVerifier {
let buf = UnsafeMutableRawBufferPointer.allocate(byteCount: Int(blockDevice.physicalBlockSize), alignment: 1)
defer {
buf.deallocate()
}
// metadataRead will throw...
try blockDevice.metadataRead(into: buf, startingAt: 0, length: Int(blockDevice.physicalBlockSize))
// but read will work.
// try await blockDevice.read(into: buf, startingAt: 0, length: Int(blockDevice.physicalBlockSize))
// ... return dummy file here (won't reach this point because metadataRead throws)
}
I'm observing this behavior on both macOS 15.5 (24F74) and macOS 15.6 beta 3 (24G5074c).
Has anyone been able to get metadataRead to work? I see it used in Apple's msdos FSKit implementation so it seems like it has to work at some level.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
We're interested in adopting App Sandbox in an app distributed outside of the Mac App Store. However, we're hitting a bit of a roadblock and it doesn't seem like either of the techniques described in that post can be used in a reasonable way.
For background, this is a third-party launcher for a cross-platform Java game that, among other things, makes it easier for users to mod the game. Users generally download mods as .jar files and place them in a certain directory. In some cases, these mods contain native dynamic libraries (e.g. a .dylib) as part of their code. In general, the .dylib is extracted from the contents of the .jar to some temporary location, loaded, and then deleted once the game closes (the exact details, like the actual temporary location, depends on the mod).
App Sandbox greatly interests us in this case because it can limit the damage that a compromised mod could do, and in my testing the functionality of most mods still works with it enabled. However, sandboxed apps quarantine every file they write to by default. Unfortunately, most mods are created by individual developers who don't notarize their libraries (their mods are generally cross-platform, and they're likely just using third-party code that they bundle with the mod but don't sign or notarize). [1] This means that a mod that loads a dynamic library as described above triggers Gatekeeper as described in the documentation if the app is sandboxed, but does not if the sandbox is disabled.
Even worse, a user often can't bypass the warning even if they trust the mod because the extracted library is usually a temporary file, and generally is deleted after the failure (which usually causes the game to crash and thus close). By the time they try to approve the code in System Settings, the file is gone (and even if they could approve it, this approval wouldn't stick next time they launch the game).
In theory it would work to use an unsandboxed XPC service to remove the quarantine and let the libraries through. However, this is easier said than done. We don't control the mods' code or how they go about loading whatever code they need, which limits what we can do.
[1] And in some cases, people like to play old versions of the game with old mods, and the versions they're using might've been released before notarization was even a thing.
The closest thing I can think of to a solution is injecting code into the Java process that runs code to call out to the XPC service to remove the quarantine before a library loads (e.g. before any calls to dlopen using dyld interposition). A prototype I have... works... but this seems really flimsy, I've read that interposition isn't meant to be used in non-dev tools, and if there's a better solution I'd certainly prefer that over this.
Other things we've tried have significant downsides:
com.apple.security.files.user-selected.executable requires user selection in a file picker, and seems to be more blunt than just allowing libraries/plugins which might lead to a sandbox escape [2]
Adding the app to the "Developer Tools" section in System Settings > Privacy & Security allows the libraries to load automatically, but requires users to add the app manually and also sounds like it would make a sandbox escape very easy [2]
Oh, and I also submitted an enhancement request for an entitlement/similar that would allow these libraries to load (FB13795828) but it was returned as "no plans to address" (which honestly wasn't that surprising).
[2] My understanding is that if a sandboxed process loads libraries, the library code would still be confined by the sandbox because it's still running in the sandboxed process. But if a sandboxed process can write and open a non-quarantined app, that app would not be within the confines of the sandbox. So basically we want to somehow allow the libraries to load but not allow standalone executables to run outside the sandbox.
In general the game and almost all popular mods I've tested work with App Sandbox enabled, except for this Gatekeeper snag. It would be a shame to completely abandon App Sandbox for this reason if everything else can be made to work.
This situation seems not super common, but documentation does say
When your sandboxed app launches for the first time, macOS creates a sandbox container on the file system (in ~/Library/Containers) and associates it with your app. Your app has full read and write access to its sandbox container, and can run programs located there as well.
which leaves me wondering whether the Gatekeeper prompt is even intended behavior since the libraries are in the sandbox container and written by the app. (By the way, my testing of the claim that apps can run programs in their sandbox container didn't seem to confirm what the documentation said, even without quarantine - FB15963761). Though, given the other documentation page I linked above which more directly references Gatekeeper and quarantined plug-ins, I doubt this is a bug.
I suppose the final question is, is this just a situation where App Sandbox won't work (at least in any supported way)? Or is there perhaps some technique we're missing?
My high-level goal is to add support for Game Mode in a Java game, which launches via a macOS "launcher" app that runs the actual java game as a separate process (e.g. using the java command line tool).
I asked this over in the Graphics & Games section and was told this, which is why I'm reposting this here.
I'm uncertain how to speak to CLI tools and Java games launched from a macOS app. These sound like security and sandboxing questions which we recommend you ask about in those sections of the forums.
The system seems to decide whether to enable Game Mode based on values in the Info.plist (e.g. for LSApplicationCategoryType and GCSupportsGameMode). However, the child process can't seem to see these values. Is there a way to change that?
(The rest of this post is copied from my other forums post to provide additional context.)
Imagine a native macOS app that acts as a "launcher" for a Java game.** For example, the "launcher" app might use the Swift Process API or a similar method to run the java command line tool (lets assume the user has installed Java themselves) to run the game.
I have seen How to Enable Game Mode. If the native launcher app's Info.plist has the following keys set:
LSApplicationCategoryType set to public.app-category.games
LSSupportsGameMode set to true (for macOS 26+)
GCSupportsGameMode set to true
The launcher itself can cause Game Mode to activate if the launcher is fullscreened. However, if the launcher opens a Java process that opens a window, then the Java window is fullscreened, Game Mode doesn't seem to activate. In this case activating Game Mode for the launcher itself is unnecessary, but you'd expect Game Mode to activate when the actual game in the Java window is fullscreened.
Is there a way to get Game Mode to activate in the latter case?
** The concrete case I'm thinking of is a third-party Minecraft Java Edition launcher, but the issue can also be demonstrated in a sample project (FB13786152). It seems like the official Minecraft launcher is able to do this, though it's not clear how. (Is its bundle identifier hardcoded in the OS to allow for this? Changing a sample app's bundle identifier to be the same as the official Minecraft launcher gets the behavior I want, but obviously this is not a practical solution.)
Topic:
Privacy & Security
SubTopic:
General
Tags:
Games
Inter-process communication
macOS
Performance
Imagine a native macOS app that acts as a "launcher" for a Java game.** For example, the "launcher" app might use the Swift Process API or a similar method to run the java command line tool (lets assume the user has installed Java themselves) to run the game.
I have seen How to Enable Game Mode. If the native launcher app's Info.plist has the following keys set:
LSApplicationCategoryType set to public.app-category.games
LSSupportsGameMode set to true (for macOS 26+)
GCSupportsGameMode set to true
The launcher itself can cause Game Mode to activate if the launcher is fullscreened. However, if the launcher opens a Java process that opens a window, then the Java window is fullscreened, Game Mode doesn't seem to activate. In this case activating Game Mode for the launcher itself is unnecessary, but you'd expect Game Mode to activate when the actual game in the Java window is fullscreened.
Is there a way to get Game Mode to activate in the latter case?
** The concrete case I'm thinking of is a third-party Minecraft Java Edition launcher, but the issue can also be demonstrated in a sample project (FB13786152). It seems like the official Minecraft launcher is able to do this, though it's not clear how. (Is its bundle identifier hardcoded in the OS to allow for this? Changing a sample app's bundle identifier to be the same as the official Minecraft launcher gets the behavior I want, but obviously this is not a practical solution.)
I've gotten to the point where I can use the mount(8) command line tool and the -t option to mount a file system using my FSKit file system extension, in which case I can see a process for my extension launch, probe, and perform the other necessary actions.
However, when plugging in my USB flash drive or trying to mount with diskutil mount, the file system does not mount:
$ diskutil mount disk20s3
Volume on disk20s3 failed to mount
If you think the volume is supported but damaged, try the "readOnly" option
$ diskutil mount readOnly disk20s3
Volume on disk20s3 failed to mount
If you think the volume is supported but damaged, try the "readOnly" option
Initially I thought it would be enough to just implement probeExtension(resource:replyHandler:) and the system would handle the rest, but this doesn't seem to be the case. Even a trivial implementation that always returns .usable doesn't cause the system to use my FSModule, even though I've enabled my extension in System Settings > General > Login Items & Extensions > File System Extensions.
From looking at some of the open source msdos and Disk Arb code, it seems like my app extension needs to list FSMediaTypes to probe. I eventually tried putting this in my Info.plist of the app extension:
<key>FSMediaTypes</key>
<dict>
<key>EBD0A0A2-B9E5-4433-87C0-68B6B72699C7</key>
<dict>
<key>FSMediaProperties</key>
<dict>
<key>Content Hint</key>
<string>EBD0A0A2-B9E5-4433-87C0-68B6B72699C7</string>
<key>Leaf</key>
<true/>
</dict>
</dict>
<key>0FC63DAF-8483-4772-8E79-3D69D8477DE4</key>
<dict>
<key>FSMediaProperties</key>
<dict>
<key>Content Hint</key>
<string>0FC63DAF-8483-4772-8E79-3D69D8477DE4</string>
<key>Leaf</key>
<true/>
</dict>
</dict>
<key>Whole</key>
<dict>
<key>FSMediaProperties</key>
<dict>
<key>Leaf</key>
<true/>
<key>Whole</key>
<true/>
</dict>
</dict>
<key>ext4</key>
<dict>
<key>FSMediaProperties</key>
<dict>
<key>Content Hint</key>
<string>ext4</string>
<key>Leaf</key>
<true/>
</dict>
</dict>
</dict>
</plist>
(For reference, the partition represented by disk20s3 has a Content Hint of 0FC63DAF-8483-4772-8E79-3D69D8477DE4 and Leaf is True which I verified using IORegistryExplorer.app from the Xcode additional tools.)
Looking in Console it does appear now that the system is trying to use my module (ExtendFS_fskit) to probe when I plug in my USB drive, but I never see a process for my extension actually launch when trying to attach to it from Xcode by name (unlike when I use mount(8), where I can do this). However I do see a Can't find the extension for <private> error which I'm not sure is related but does sound like the system can't find the extension for some reason.
The below messages are when filtering for "FSKit":
default 19:14:53.455826-0400 diskarbitrationd probed disk, id = /dev/disk20s3, with ExtendFS_fskit, ongoing.
default 19:14:53.456038-0400 fskitd Incomming connection, entitled 1
default 19:14:53.456064-0400 fskitd [0x7d4172e40] activating connection: mach=false listener=false peer=true name=com.apple.filesystems.fskitd.peer[350].0x7d4172e40
default 19:14:53.456123-0400 fskitd Hello FSClient! entitlement yes
default 19:14:53.455902-0400 diskarbitrationd [0x7461d8dc0] activating connection: mach=true listener=false peer=false name=com.apple.filesystems.fskitd
default 19:14:53.456151-0400 diskarbitrationd Setting remote protocol to all XPC
default 19:14:53.456398-0400 fskitd About to get current agent for 501
default 19:14:53.457185-0400 diskarbitrationd probed disk, id = /dev/disk20s3, with ExtendFS_fskit, failure.
error 19:14:53.456963-0400 fskitd -[fskitdXPCServer applyResource:targetBundle:instanceID:initiatorAuditToken:authorizingAuditToken:isProbe:usingBlock:]: Can't find the extension for <private>
(I only see these messages after plugging my USB drive in. When running diskutil mount, I see no messages in the console when filtering by FSKit, diskarbitrationd, or ExtendFS afterward. It just fails.)
Is there a step I'm missing to get this to work, or would this be an FSKit bug/current limitation?
I’m attempting to make an app that uses Disk Arbitration to intercept a disk mount (by creating and returning a dissenter in the appropriate callback) and then mount the disk with certain options (specifically, read-only, nobrowse, or both, depending on user options). For example:
DADiskMountWithArguments(disk, nil, DADiskMountOptions(kDADiskMountOptionDefault), nil, nil, kReadOnly)
…where kReadOnly is a pointer to an array only containing a “rdonly” CFString.
While DADiskMountWithArguments seems to be usable in a sandboxed app for disk images, it doesn’t work when the disk is an external disk (e.g. connected via USB). I see lines like this in Console.app when this happens:
Sandbox denied authorizing right 'system.volume.external.mount' by client '/path/to/exe' [17934] (engine 580)
I’ve identified two workarounds that allow this to work in a sandbox, but both have their own problems:
If a LaunchDaemon (even a sandboxed one, which is required for registration with SMAppService.daemon from the sandboxed app) does the call to DADiskMountWithArguments, it will succeed. But App Store policies don’t allow escalation to root.
If I use the undocumented entitlement com.apple.security.temporary-exception.sbpl with a value of (allow authorization-right-obtain (right-name "system.volume.external.mount")), the mount works without escalation to root. But I understand that App Review is likely to reject the use of this entitlement, and that this entitlement isn't supported to begin with.
Specifically, these are the behaviors I see on macOS Sequoia 15.3.1 and Xcode 16.2.
Since I would like to try to publish this app on the App Store, neither of these seem like acceptable solutions.
I don’t see why this should be restricted if the sandboxed app is not declaring a special path (i.e. the path in DADiskMountWithArguments is set to nil) and still does not have access to the mounted filesystem - am I missing something/is there a way to accomplish this?
Topic:
App & System Services
SubTopic:
Core OS
Tags:
Entitlements
macOS
App Sandbox
Disk Arbitration
Is it possible to build DocC documentation on a machine without Xcode or xcodebuild, such as a Linux machine? Or is this more of a feature specific to Xcode, and not Swift in general?