Skip to content

Commit

Permalink
Ensure osxphotos is responsible process by launching disclaimed (#1587)
Browse files Browse the repository at this point in the history
When osxphotos accesses the Photos library the macOS TCC system
will check the responsible process for permission to access the
library, and if none has been given, the user is asked to give
permission via a dialog.

Normally, when an executable is launched from Terminal or iTerm,
the responsible app is the terminal app itself, not osxphotos,
which means the user needs to give the terminal app the permission
to access the Photos library, effectively giving any command
executed in the terminal at any point access to the library.

Worse yet, if the user has on some prior occasion denied the
terminal app this access, then osxphotos will not get the needed
access either, which has been a source for confusion and failures
for users.

We now re-launch osxphotos at startup with a special spawn flag
that ensures that osxphotos is the responsible process. This will
result in TCC dialogs and permissions specific to osxphotos.
  • Loading branch information
torarnv committed Jun 22, 2024
1 parent 0c4bc63 commit 39f16e4
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 3 deletions.
60 changes: 60 additions & 0 deletions disclaim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

extern "C" {
int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs, int disclaim)
__attribute__((availability(macos,introduced=10.14),weak_import));
char ***_NSGetArgv();
}

#define CHECK_SPAWN(expr) \
if (int err = (expr)) { \
posix_spawnattr_destroy(&attr); \
fprintf(stderr, "[disclaim] %s: %s", #expr, strerror(err)); \
exit(err); \
}

/*
Re-launches the process with disclaimed responsibilities,
effectively making it responsible for its own permissions.
Based on https://www.qt.io/blog/the-curious-case-of-the-responsible-process
*/
extern "C" void disclaim()
{
posix_spawnattr_t attr;
CHECK_SPAWN(posix_spawnattr_init(&attr));

// Behave as exec
short flags = POSIX_SPAWN_SETEXEC;

// Reset signal mask
sigset_t no_signals;
sigemptyset(&no_signals);
CHECK_SPAWN(posix_spawnattr_setsigmask(&attr, &no_signals));
flags |= POSIX_SPAWN_SETSIGMASK;

// Reset all signals to their default handlers
sigset_t all_signals;
sigfillset(&all_signals);
CHECK_SPAWN(posix_spawnattr_setsigdefault(&attr, &all_signals));
flags |= POSIX_SPAWN_SETSIGDEF;

CHECK_SPAWN(posix_spawnattr_setflags(&attr, flags));

if (__builtin_available(macOS 10.14, *)) {
// Disclaim TCC responsibilities for parent, making
// the launched process the responsible process.
if (responsibility_spawnattrs_setdisclaim)
CHECK_SPAWN(responsibility_spawnattrs_setdisclaim(&attr, 1));
}

pid_t pid = 0;
char **argv = *_NSGetArgv();
extern char **environ;
CHECK_SPAWN(posix_spawnp(&pid, argv[0], nullptr, &attr, argv, environ));
}
24 changes: 24 additions & 0 deletions disclaim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
def _pyi_rthook():
import sys
if sys.platform != 'darwin':
return

# Avoid redundant disclaims
import os
if os.environ.get('PYI_DISCLAIMED'):
return
os.environ['PYI_DISCLAIMED'] = '1'

# The bootloader has cleared the _MEIPASS2 environment variable by the
# time we get here, which means re-launching the executable disclaimed
# will unpack the binary again. To avoid this we reset _MEIPASS2 again,
# so that our re-launch will pick up at second stage of the bootstrap.
os.environ['_MEIPASS2'] = sys._MEIPASS

import ctypes
library_path = os.path.join(sys._MEIPASS, 'libdisclaim.dylib')
libdisclaim = ctypes.cdll.LoadLibrary(library_path)
libdisclaim.disclaim()

_pyi_rthook()
del _pyi_rthook
5 changes: 4 additions & 1 deletion make_cli_exe.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
# If you need to install pyinstaller:
# python3 -m pip install --upgrade pyinstaller

pyinstaller osxphotos.spec
set -e
mkdir -p build
clang -shared -mmacosx-version-min=10.12 disclaim.cpp -o build/libdisclaim.dylib
pyinstaller osxphotos.spec
4 changes: 2 additions & 2 deletions osxphotos.spec
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ block_cipher = None
a = Analysis(
["cli.py"],
pathex=[pathex],
binaries=[],
binaries=[("build/libdisclaim.dylib", ".")],
datas=datas,
hiddenimports=["pkg_resources.py2_warn"],
hookspath=[],
runtime_hooks=[],
runtime_hooks=["disclaim.py"],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
Expand Down

0 comments on commit 39f16e4

Please sign in to comment.