Investigate moving away from DO (Distributed Object) as IPC mechanism
MacVim currently uses Distributed Object as a way to communicate between individual Vim processes and MacVim but it's long been deprecated by Apple. See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DistrObjects/DistrObjects.html. As such, all usages of NSConnection are currently throwing deprecation warning.
Instead, Apple's preferred APIs are the XPC system: https://developer.apple.com/documentation/xpc
We should investigate whether to move off DO to either Apple's XPC or another IPC mechanism (for example, Neovim has its own IPC mechanism via msgpack). Since the DO implementation is the underlying system that we talks between MacVim GUI and Vim process, and there are a few quirks and back-and-forth messaging, we should make sure it keeps working (some edge cases includes window resizing with/without guioption-k, fullscreen, input, etc), and latency won't be affected as a result.
Also, occasionally we see error messages with DO (#1005), and I have seen issues with running unit tests locally if I have certain settings that seem to stem from DO init race connection, so hopefully we can eliminate those as well.
I'm not sure that XPC wouldn't be visible to other applications.
Security risks I see with XPC itself:
- Third party app connects as a client of a MacVim XPC server. Then it could do fuzzing or attack the server, which is a huge risk.
- there's publicly available XPC sniffers working under the same user, so everything you write in the app could be listened via background sniffer which can be a malicious part of a legit application.
Hardening the first point is easy, but I don't like an idea to loose privacy on what am I typing on the second.
source: https://github.com/macvim-dev/macvim/issues/1594#issuecomment-3213249517
If you use a bundled XPC service (which MacVim would) then other apps can’t access the service. On top of that, you can easily restrict connections to ones signed by a particular development team using xpc_connection_set_peer_lightweight_code_requirement. I can almost guarantee you that all your keystrokes are already passing through several Apple XPC services on their way to an app anyway – if XPC had serious security or privacy concerns, then we’d have a much bigger issue than MacVim leaking typed text.
As MacVim spawns a Vim process which is working as a backend, this second process must have an access to this XPC connection. I'd like to see a pseudocode solution which disables accepting a new connection which excludes race conditions (e.g. vulnerable algorithm: enable accepting connections while Vim starts, then disable it).
Code signature check would work only on a binary downloaded from GitHub (one way or another). I suppose method you use (homebrew cask) does exactly this. I can locally sign with my local non-developer signature, and this protection won't work.
Besides, method you proposed is available only on macOS 14.4+, and MacVim supports quite wide range of macOS versions including quite old ones. GitHub binary supports 10.13 (High Sierra) and MacPorts (read it as "build from source") supports even 10.8 without any patches (only patches are for Python and TCL dependencies)
PS: There's plenty of tech talks, CVEs and published papers on the security of XPC. PPS: There's few secure XPC communication implementations (as XPC allows), which can be applied in one way or another
As
MacVimspawns aVimprocess which is working as a backend, this second process must have an access to this XPC connection. I'd like to see a pseudocode solution which disables accepting a new connection which excludes race conditions (e.g. vulnerable algorithm: enable accepting connections while Vim starts, then disable it).
Instead of spawning MacVim.app/Contents/MacOS/Vim, I imagine that it’d spawn MacVim.app/Contents/XPCServices/org.vim.MacVim.Backend.xpc/Contents/MacOS/org.vim.MacVim.Backend (or something like that). Being a bundled service, only connections made by MacVim.app/Contents/MacOS/MacVim would be permitted. If you really only want to accept the first connection, then a simple atomic boolean global set after the first connection has been accepted would be fine.
I can locally sign with my local non-developer signature, and this protection won't work. Besides, method you proposed is available only on
macOS 14.4+, and MacVim supports quite wide range of macOS versions including quite old ones.
Ah, good points, I hadn’t considered that.
PS: There's plenty of tech talks, CVEs and published papers on the security of XPC. PPS: There's few secure XPC communication implementations (as XPC allows), which can be applied in one way or another
I maintain that a vulnerability in XPC shouldn’t be part of MacVim’s threat model. The entire OS relies on XPC for innumerable things: /System/Library alone contains 429 different XPC services. Being concerned about introducing XPC because XPC can be exploited is akin to being concerned about keeping data in your process’s address space because the kernel can be exploited – if that’s something you’re worried about, then you should be targeting a platform other than macOS.
As
MacVimspawns aVimprocess which is working as a backend, this second process must have an access to this XPC connection. I'd like to see a pseudocode solution which disables accepting a new connection which excludes race conditions (e.g. vulnerable algorithm: enable accepting connections while Vim starts, then disable it).Instead of spawning
MacVim.app/Contents/MacOS/Vim, I imagine that it’d spawnMacVim.app/Contents/XPCServices/org.vim.MacVim.Backend.xpc/Contents/MacOS/org.vim.MacVim.Backend(or something like that). Being a bundled service, only connections made byMacVim.app/Contents/MacOS/MacVimwould be permitted. If you really only want to accept the first connection, then a simple atomic boolean global set after the first connection has been accepted would be fine.
I agree in general. On an implementer side, I'd consulted also on some security-aware implementations of XPC connections.
PS: There's plenty of tech talks, CVEs and published papers on the security of XPC. PPS: There's few secure XPC communication implementations (as XPC allows), which can be applied in one way or another
I maintain that a vulnerability in XPC shouldn’t be part of MacVim’s threat model. The entire OS relies on XPC for innumerable things:
/System/Libraryalone contains 429 different XPC services. Being concerned about introducing XPC because XPC can be exploited is akin to being concerned about keeping data in your process’s address space because the kernel can be exploited – if that’s something you’re worried about, then you should be targeting a platform other than macOS.
I'd disagree on this attitude. Security must always go first and there's numerous easy ways to make it wrong, especially with XPC and similar facilities.
Also there's numerious ways to make it right and there's few secure XPC communication implementations out there.
PS: There's plenty of tech talks, CVEs and published papers on the security of XPC.
I have never seen any suggestion that XPC itself is vulnerable. Can you be more specific @eirnym about a concrete attack? XPC is used throughout macOS exclusively. It's based on Mach ports, and you can't just casually "listen" to a port discussion between two processes. There are usages of XPC that are insecure, sure, but just saying XPC is insecure isn't very useful.
Instead of spawning
MacVim.app/Contents/MacOS/Vim, I imagine that it’d spawnMacVim.app/Contents/XPCServices/org.vim.MacVim.Backend.xpc/Contents/MacOS/org.vim.MacVim.Backend(or something like that). Being a bundled service, only connections made byMacVim.app/Contents/MacOS/MacVimwould be permitted. If you really only want to accept the first connection, then a simple atomic boolean global set after the first connection has been accepted would be fine.
I don't think we would spawn the process that way, as we need some way to control how the Vim process runs. I don't think this is really what XPC is designed to do anyway (Vim isn't exactly an ephemeral service). I would likely set up an XPC endpoint to pass the messages back and forth between MacVim and Vim instead.
This isn't really the core issue anyway. On macOS XPC endpoints are separated by app bundles. You can't connect to an XPC end point within an app bundle from an external app (this is what @lunacookies meant by "bundled XPC service"). An exception would be if you go to MacVim, open a terminal within Vim using :term, then launch a program, that program can indeed talk to an XPC end point (since it would be running the app bundle boundary) but we will likely set it up so only Vim processes spawned by MacVim can talk to it.
there's publicly available XPC sniffers working under the same user, so everything you write in the app could be listened via background sniffer which can be a malicious part of a legit application.
Are there any sniffers that don't require sudo (meaning the user has agreed to it) and can work with a hardened runtime app? If either is not true, then it's not important. (Otherwise sure, LLDB can attach to a process and listen to it as a debugger)
Third party app connects as a client of a MacVim XPC server. Then it could do fuzzing or attack the server, which is a huge risk.
I'm sorry to disappoint you but MacVim already allows that, with the current DO mechanism. Anyone can connect to MacVim and this was in fact the source of https://github.com/macvim-dev/macvim/security/advisories/GHSA-9jgj-jfwg-99fv. MacVim just has to trust that the other client is Vim and if it pops up a window and the user didn't expect it that would be a sign that something is wrong. This is part of why I want to move away from it.
At least with the current implementation (or any future implementation), if MacVim fails to initialize the server it will warn you, so it should not be able to interject this silently.
Types of XPC service required
I think there are really two types of usages at play here. When MacVim spawns its own Vim (e.g. using Cmd-N within MacVim), it can talk through XPC through its own internal app endpoint which cannot be intercepted or messed with by an external app. If you know of a zero day exception, file a bug to Apple security team. Otherwise this is completely secure.
However, MacVim doesn't always spawn its own Vim. Currently when you 1) launch Vim using the mvim or vim -g command, or 2) when you launch Vim and then use the :gui command, you are using MacVim to serve as a GUI for a Vim that was launched and owned by the terminal app. In order to support this, you need some ways for MacVim to accept commands by an external app (in this case the app bundle would be Terminal.app / Ghostty.app / iTerm / etc).
Another reason why a global server to accept incoming commands is to handle vim --servername SOME_SERVER --remote-send <some_cmds> commands. Note that this design of Vim is… inherently insecure. When I do this revamp (or probably before) I'm thinking of adding a setting for users to opt-in to accept remote server commands. But either way it requires some server listening to literally anyone on the local computer who cares to send Vim a command which could really do anything.
Anyway, to register an XPC server that could listen to external applications, XPC requires you setting up a global XPC service. This is where I personally do not love the idea of using XPC, because the idea of "installing" a global service for something that you only use when the app is open is quite unappealing to me. Right now with DO you essentially only register the DO server when the app is active. I think most users (including me) find the idea of active service for an app that you use once in a while that may or may not be doing stuff in the background quite odd. I'm still thinking of alternative here. For example, Neovim just uses Unix sockets here (which isn't any more secure than XPC, mind you, but it's a randomly generated path at least).
Authentication
For XPC (or Mach messages in general, which XPC is a wrapper of), there are some ways to authenticate the incoming messages for the global service. My understanding is the generic way to do so is by using audit tokens which allows you to verify the PID of the app to make sure it's indeed the Vim that you want (this sidesteps the code signature requirement). That said, I personally believe we should make MacVim resilient to malicious clients to be fundamentally secure. Most of the security model should be in Vim anyway as it's the process that actually needs to be reading/writing/touching files. MacVim itself should be serving as a GUI that coordinates things. We would also necessarily need this if we want MacVim to eventually talk to arbitrary Neovim clients.
Vim isn't exactly an ephemeral service
Ah, good point, I didn’t consider that.
the idea of "installing" a global service for something that you only use when the app is open is quite unappealing to me
On macOS Ventura and later, you can use the Service Management framework to bundle a Launch Agent with MacVim, no global installation needed.
On macOS Ventura and later, you can use the Service Management framework to bundle a Launch Agent with MacVim, no global installation needed.
I believe it still shows up in the "Login Items & Extensions" list in the Settings UI, which is what I personally dislike. I personally quite dislike the phrasing there as it implies that MacVim can perform actions in the background even if it's not open, even though I only need the agent to forward some messages while MacVim is open.
It also doesn't work on older macOS versions which we support.
MacVim currently uses Distributed Object as a way to communicate between individual Vim processes and MacVim but it's long been deprecated by Apple. See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DistrObjects/DistrObjects.html. As such, all usages of
NSConnectionare currently throwing deprecation warning.Instead, Apple's preferred APIs are the XPC system: https://developer.apple.com/documentation/xpc
We should investigate whether to move off DO to either Apple's XPC or another IPC mechanism (for example, Neovim has its own IPC mechanism via msgpack). Since the DO implementation is the underlying system that we talks between MacVim GUI and Vim process, and there are a few quirks and back-and-forth messaging, we should make sure it keeps working (some edge cases includes window resizing with/without guioption-k, fullscreen, input, etc), and latency won't be affected as a result.
Also, occasionally we see error messages with DO (#1005), and I have seen issues with running unit tests locally if I have certain settings that seem to stem from DO init race connection, so hopefully we can eliminate those as well.