From 025d6814b4bf68bffc5a4810e8c7143a0cf713d1 Mon Sep 17 00:00:00 2001 From: Muffin Date: Fri, 16 Feb 2024 22:18:38 -0600 Subject: [PATCH] Flip some Electron fuses for security --- package-lock.json | 121 ++++++++++++++++++++++++++++++++++++ package.json | 1 + release-automation/build.js | 50 +++++++++++++++ 3 files changed, 172 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2d94d8cf..9fc61b95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@babel/core": "^7.23.9", "@babel/preset-env": "^7.12.16", "@babel/preset-react": "^7.13.13", + "@electron/fuses": "^1.7.0", "@electron/get": "^3.0.0", "@electron/notarize": "^2.2.1", "adm-zip": "^0.5.10", @@ -1993,6 +1994,126 @@ "node": "*" } }, + "node_modules/@electron/fuses": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.7.0.tgz", + "integrity": "sha512-mfhLoZGQdqrSU/SeOFBs6r+D7g1tYiVs2C/hh7t3NFQ0chcXGoWrrad17rCQL1ImNJuCXs4cu23YBj5CAnj5SA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@electron/fuses/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@electron/fuses/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@electron/fuses/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/fuses/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/fuses/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/fuses/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/fuses/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@electron/get": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.0.0.tgz", diff --git a/package.json b/package.json index 129d16ee..9243b24f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@babel/core": "^7.23.9", "@babel/preset-env": "^7.12.16", "@babel/preset-react": "^7.13.13", + "@electron/fuses": "^1.7.0", "@electron/get": "^3.0.0", "@electron/notarize": "^2.2.1", "adm-zip": "^0.5.10", diff --git a/release-automation/build.js b/release-automation/build.js index a1d1c009..1e8f8398 100644 --- a/release-automation/build.js +++ b/release-automation/build.js @@ -3,6 +3,7 @@ const fs = require('fs'); const builder = require('electron-builder'); const electronNotarize = require('@electron/notarize'); const electronGet = require('@electron/get'); +const electronFuses = require('@electron/fuses'); const AdmZip = require('adm-zip'); const packageJSON = require('../package.json'); @@ -46,6 +47,54 @@ const getPublish = () => process.env.GH_TOKEN ? ({ repo: 'desktop' }) : null; +const addElectronFuses = async (context) => { + // Have to apply fuses manually per https://github.com/electron-userland/electron-builder/issues/6365 + // This code is based on the comments on that issue + + const platformName = context.electronPlatformName; + const getExecutableName = () => { + if (platformName === 'win32') return `${context.packager.appInfo.productFilename}.exe`; + if (platformName === 'darwin') return `${context.packager.appInfo.productFilename}.app`; + if (platformName === 'linux') return packageJSON.name; + throw new Error(`Unknown platform: ${platformName}`); + }; + + const electronBinaryPath = pathUtil.join(context.appOutDir, getExecutableName()); + console.log(`Flipping fuses in ${electronBinaryPath}`); + + await electronFuses.flipFuses(electronBinaryPath, { + // Necessary for building on Apple Silicon + resetAdHocDarwinSignature: platformName === 'darwin', + + version: electronFuses.FuseVersion.V1, + + // https://www.electronjs.org/blog/statement-run-as-node-cves + // Because our app is likely to have access to the user's microphone and camera, we should make it slightly + // more difficult for a local attacker to elevate permissions. This probably isn't perfect and any bypasses + // won't be eligible for a bug bounty from us (if you have a local attacker, you've already lost). + [electronFuses.FuseV1Options.RunAsNode]: false, + [electronFuses.FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [electronFuses.FuseV1Options.EnableNodeCliInspectArguments]: false, + + // - EnableCookieEncryption should be considered in the future once we analyze performance, backwards + // compatibility, make sure data doesn't get lost on uninstall, unsigned versions, etc. + // - electron-builder does not support EnableEmbeddedAsarIntegrityValidation + // https://github.com/electron-userland/electron-builder/issues/6930 + // - OnlyLoadAppFromAsar doesn't seem usable because we need to migrate pre-1.9.0 data from file:// + // - LoadBrowserProcessSpecificV8Snapshot is not useful for us. + // - GrantFileProtocolExtraPrivileges should be considered when we update Electron. + // This would've prevented CVE-2023-40168. + }); +}; + +const afterPack = async (context) => { + // For macOS, only apply the fuses at the very end of the universal build, not the individual + // Intel and Apple Silicon builds. + if (context.electronPlatformName !== 'darwin' || context.arch === Arch.universal) { + await addElectronFuses(context) + } +}; + const build = async ({ platformName, // String that indexes into Platform[...] platformType, // Passed as first argument into platform.createTarget(...) @@ -78,6 +127,7 @@ const build = async ({ tw_warn_legacy: isProduction, tw_update: isProduction && manageUpdates }, + afterPack, ...extraConfig, ...await prepare(archName) };