Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve MacOS privilege escalation #50

Open
caesay opened this issue Mar 12, 2024 · 0 comments
Open

Improve MacOS privilege escalation #50

caesay opened this issue Mar 12, 2024 · 0 comments
Labels
enhancement New feature or request help wanted Extra attention is needed os:mac

Comments

@caesay
Copy link
Member

caesay commented Mar 12, 2024

Current approach

Currently we use applescript (eg. do shell script ... with administrator privileges) if root is required (eg. the app is in /Applications). This is not recommended by apple, and requires that the application request a password each time the user installs an update. The dialog shown is a very generic "oascript is requesting escalation" dialog which we can't customise, and does not support fingerprint auth.

In general, it would be nice if we could avoid the prompt altogether, or barring that, show a nicer dialog and/or enable fingerprints.

A review of the available alternatives

AuthorizationExecuteWithPrivileges is pretty much the perfect solution - to execute a binary elevated by filepath & arguments, but this has been deprecated for a long time (since 10.7) and I believe it's no longer functional in recent os versions.

SMJobSubmit is the closest replacement I think, and it's still used by Sparkle despite also being deprecated since 10.10. According to the comment in the sparkle codebase:

SMJobSubmit is deprecated but is the only way to submit a non-permanent helper and allows us to submit to user domain without requiring authorization

I assume since it's still used by Sparkle this API is still functional, but I suppose it could be removed at any time.

SMJobBless is a more recent/recommended API, but it comes with it's own challenges. SMJobBless results in the helper binary being permanently copied into a system location, and being run permanently as a launchd service. You need to communicate with this service using IPC / XPC. If the process exits, it will automatically be restarted by the OS. Despite SMJobBless creating a strong 1-1 relationship between a root level helper and it's applet, this helper service will persist even if the app is removed. It's not possible for this helper to be shared between apps, because they are connected by code signing identity. I don't think it's acceptable or reasonable to install a system service once per Velopack app, that runs as root, all the time, even if we are not performing an update or the app has been removed. Additionally, SMJobBless was also deprecated (as of 13.0) and the only official way to uninstall a helper (SMJobRemove) is also deprecated (as of 10.10).

Custom launchd daemon could be an option. A custom daemon could be installed/updated automatically during our .pkg postinstall script using launchctl. This has many benefits over SMJobBless. The single daemon could be shared by all velopack apps, and be run on-demand when an app update is necessary. The daemon could be configured to start on-demand, either in response to an incoming TCP connection, directory watch, or even a queue directory (eg. task will run until queue directory is empty). A queue directory would work particularly good: any apps requiring updates would simply place the downloaded update into the queue directory, the daemon would automatically launch and move it into place and then quit.

SMAppService seems similar to SMJobBless, available on 13.0+, however the daemon/helper executable lives inside the app main bundle instead of a system directory, and therefore are removed when the .app is deleted. SMAppService also provides some other api's (eg. registering app to auto-start at login).

Conclusion

If we wish to rely on a "self contained" approach, then we should continue to rely on AppleScript on < 13.0 and implement an embedded daemon via SMAppService for 13.0+. This is complex to implement, and regularly subject to changing/deprecated API's, however is probably the most secure and reliable, since each app and it's update daemon are isolated and not impacted by other apps being installed.

If installing a root launchd daemon (using a queue directory) shared by all velopack apps is an acceptable solution, then this will reduce the complexity and improve the user experience of applying updates. The daemon can be installed and updated automatically any time a .pkg is run, and no prompts will need to be shown when installing updates (mimicking windows behavior). The drawback is that all apps share a single daemon, and if velopack/macos requirements change over time this could cause compatibility issues.

Also see

@caesay caesay added enhancement New feature or request help wanted Extra attention is needed os:mac labels Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed os:mac
Projects
None yet
Development

No branches or pull requests

1 participant