|
| 1 | +#!/bin/bash |
| 2 | +# Replaces electron-builder's stock deb postinst. Naming `deb.afterInstall` in |
| 3 | +# electron-builder.yml *overrides* the default template rather than appending |
| 4 | +# to it, so the update-alternatives / mime / desktop-database blocks below are |
| 5 | +# carried over verbatim from app-builder-lib's `templates/linux/after-install.tpl`. |
| 6 | +# Only the sandbox section is ours. Keep the rest in sync when bumping |
| 7 | +# electron-builder. |
| 8 | + |
| 9 | +if type update-alternatives 2>/dev/null >&1; then |
| 10 | + # Remove previous link if it doesn't use update-alternatives |
| 11 | + if [ -L '/usr/bin/${executable}' -a -e '/usr/bin/${executable}' -a "`readlink '/usr/bin/${executable}'`" != '/etc/alternatives/${executable}' ]; then |
| 12 | + rm -f '/usr/bin/${executable}' |
| 13 | + fi |
| 14 | + update-alternatives --install '/usr/bin/${executable}' '${executable}' '/opt/${sanitizedProductName}/${executable}' 100 || ln -sf '/opt/${sanitizedProductName}/${executable}' '/usr/bin/${executable}' |
| 15 | +else |
| 16 | + ln -sf '/opt/${sanitizedProductName}/${executable}' '/usr/bin/${executable}' |
| 17 | +fi |
| 18 | + |
| 19 | +# ── Sandbox ────────────────────────────────────────────────────────────────── |
| 20 | +# |
| 21 | +# The stock template picks between Chromium's two sandboxes like this: |
| 22 | +# |
| 23 | +# if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then |
| 24 | +# chmod 4755 chrome-sandbox # no user namespaces -> SUID sandbox |
| 25 | +# else |
| 26 | +# chmod 0755 chrome-sandbox # namespaces work -> SUID not needed |
| 27 | +# fi |
| 28 | +# |
| 29 | +# That asks the right question as the wrong user, at the wrong time. A postinst |
| 30 | +# runs as root, and Ubuntu 24.04+'s `kernel.apparmor_restrict_unprivileged_userns` |
| 31 | +# restricts *unprivileged* user namespaces only — root is exempt. So the probe |
| 32 | +# succeeds during install, the else branch runs, chrome-sandbox lands as 0755, |
| 33 | +# and the first ordinary launch dies with: |
| 34 | +# |
| 35 | +# The SUID sandbox helper binary was found, but is not configured |
| 36 | +# correctly. Rather than run without sandboxing I'm aborting now. |
| 37 | +# |
| 38 | +# Two things are done about it, in order of preference. |
| 39 | +# |
| 40 | +# First, an AppArmor profile granting this binary `userns create`. That restores |
| 41 | +# the *namespace* sandbox, which is the one upstream Chromium actually develops |
| 42 | +# against; the SUID helper is a compatibility path for kernels without userns. |
| 43 | +# Shipping the profile is what Chrome, Chromium and the other Electron apps in |
| 44 | +# the archive settled on for this exact regression. |
| 45 | +# |
| 46 | +# Second, the SUID bit, as a fallback — decided by probing as an unprivileged |
| 47 | +# user rather than as root, so the answer reflects the conditions the app will |
| 48 | +# actually run under. `nobody` is the probe subject because it is the one |
| 49 | +# account guaranteed to exist and guaranteed not to be privileged. |
| 50 | +CHROME_SANDBOX='/opt/${sanitizedProductName}/chrome-sandbox' |
| 51 | +APPARMOR_PROFILE='/etc/apparmor.d/${executable}' |
| 52 | + |
| 53 | +userns_available_unprivileged() { |
| 54 | + # No unprivileged probe subject means no trustworthy answer. Report "not |
| 55 | + # available", which selects the SUID sandbox — a sandbox that is merely |
| 56 | + # unnecessary costs nothing, while wrongly skipping it aborts the app. |
| 57 | + command -v runuser >/dev/null 2>&1 || return 1 |
| 58 | + id nobody >/dev/null 2>&1 || return 1 |
| 59 | + runuser -u nobody -- unshare --user true >/dev/null 2>&1 |
| 60 | +} |
| 61 | + |
| 62 | +if [ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns 2>/dev/null)" = "1" ] \ |
| 63 | + && [ -d /etc/apparmor.d ]; then |
| 64 | + cat > "$APPARMOR_PROFILE" <<'APPARMOR_EOF' |
| 65 | +# Grants the namespace sandbox to DiskPush on kernels where |
| 66 | +# unprivileged user namespaces are restricted by AppArmor (Ubuntu 24.04+). |
| 67 | +# flags=(unconfined) keeps this a permission grant, not a confinement policy: |
| 68 | +# it does not restrict the application beyond the system default. |
| 69 | +abi <abi/4.0>, |
| 70 | +include <tunables/global> |
| 71 | +
|
| 72 | +profile ${executable} "/opt/${sanitizedProductName}/${executable}" flags=(unconfined) { |
| 73 | + userns, |
| 74 | + include if exists <local/${executable}> |
| 75 | +} |
| 76 | +APPARMOR_EOF |
| 77 | + |
| 78 | + # Best-effort. A parser that rejects the profile (an older abi, say) must |
| 79 | + # not fail the package install — the SUID fallback below still applies. |
| 80 | + if command -v apparmor_parser >/dev/null 2>&1; then |
| 81 | + apparmor_parser -r -W "$APPARMOR_PROFILE" >/dev/null 2>&1 \ |
| 82 | + || echo 'diskpush: could not load AppArmor profile; falling back to the SUID sandbox' >&2 |
| 83 | + fi |
| 84 | +fi |
| 85 | + |
| 86 | +if userns_available_unprivileged; then |
| 87 | + chmod 0755 "$CHROME_SANDBOX" || true |
| 88 | +else |
| 89 | + chmod 4755 "$CHROME_SANDBOX" || true |
| 90 | +fi |
| 91 | + |
| 92 | +if hash update-mime-database 2>/dev/null; then |
| 93 | + update-mime-database /usr/share/mime || true |
| 94 | +fi |
| 95 | + |
| 96 | +if hash update-desktop-database 2>/dev/null; then |
| 97 | + update-desktop-database /usr/share/applications || true |
| 98 | +fi |
0 commit comments