mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Fixed privilge escalation on Linux and Windows
This commit is contained in:
parent
3a3c14aab7
commit
de796a8673
3 changed files with 44 additions and 22 deletions
6
receivers/electron/package-lock.json
generated
6
receivers/electron/package-lock.json
generated
|
@ -4467,9 +4467,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
||||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -360,12 +360,12 @@ export default class Main {
|
||||||
try {
|
try {
|
||||||
Main.application = app;
|
Main.application = app;
|
||||||
const isUpdating = Updater.isUpdating();
|
const isUpdating = Updater.isUpdating();
|
||||||
const fileLogType = (isUpdating && !Updater.updateApplied) ? 'fileSync' : 'file';
|
const fileLogType = isUpdating ? 'fileSync' : 'file';
|
||||||
|
|
||||||
log4js.configure({
|
log4js.configure({
|
||||||
appenders: {
|
appenders: {
|
||||||
out: { type: 'stdout' },
|
out: { type: 'stdout' },
|
||||||
log: { type: fileLogType, filename: path.join(app.getPath('logs'), 'fcast-receiver.log'), flags: 'a', maxLogSize: '10M' },
|
log: { type: fileLogType, filename: path.join(app.getPath('logs'), 'fcast-receiver.log'), flags: 'a', maxLogSize: '5M' },
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
default: { appenders: ['out', 'log'], level: 'info' },
|
default: { appenders: ['out', 'log'], level: 'info' },
|
||||||
|
|
|
@ -6,6 +6,7 @@ import * as log4js from "log4js";
|
||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
import { Store } from './Store';
|
import { Store } from './Store';
|
||||||
import sudo from 'sudo-prompt';
|
import sudo from 'sudo-prompt';
|
||||||
|
const cp = require('child_process');
|
||||||
const extract = require('extract-zip');
|
const extract = require('extract-zip');
|
||||||
const logger = log4js.getLogger();
|
const logger = log4js.getLogger();
|
||||||
|
|
||||||
|
@ -52,7 +53,6 @@ export class Updater {
|
||||||
private static channelVersion: string = null;
|
private static channelVersion: string = null;
|
||||||
|
|
||||||
public static isDownloading: boolean = false;
|
public static isDownloading: boolean = false;
|
||||||
public static updateApplied: boolean = false;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private static async fetchJSON(url: string): Promise<any> {
|
private static async fetchJSON(url: string): Promise<any> {
|
||||||
|
@ -93,11 +93,6 @@ export class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async applyUpdate(src: string, dst: string) {
|
private static async applyUpdate(src: string, dst: string) {
|
||||||
// Sanity removal protection check (especially under admin)
|
|
||||||
if (!dst.includes('fcast-receiver')) {
|
|
||||||
throw `Aborting update applying due to possible malformed path: ${dst}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.accessSync(dst, fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK);
|
fs.accessSync(dst, fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK);
|
||||||
|
|
||||||
|
@ -105,15 +100,20 @@ export class Updater {
|
||||||
process.noAsar = true
|
process.noAsar = true
|
||||||
fs.rmSync(dst, { recursive: true, force: true });
|
fs.rmSync(dst, { recursive: true, force: true });
|
||||||
fs.cpSync(src, dst, { recursive: true, force: true });
|
fs.cpSync(src, dst, { recursive: true, force: true });
|
||||||
process.noAsar = false
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
if (err.code === 'EACCES') {
|
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
||||||
logger.info('Update requires admin privileges. Escalating...');
|
logger.info('Update requires admin privileges. Escalating...');
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const shell = process.platform === 'win32' ? 'powershell' : '';
|
let command: string;
|
||||||
const command = `${shell} rm -rf ${dst}; ${shell} cp -rf ${src} ${dst}`
|
if (process.platform === 'win32') {
|
||||||
|
// Using native cmd.exe seems to create less issues than using powershell...
|
||||||
|
command = `rmdir /S /Q "${dst}" & xcopy /Y /E "${src}" "${dst}"`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
command = `rm -rf '${dst}'; cp -rf '${src}' '${dst}'`;
|
||||||
|
}
|
||||||
|
|
||||||
sudo.exec(command, { name: 'FCast Receiver' }, (error, stdout, stderr) => {
|
sudo.exec(command, { name: 'FCast Receiver' }, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -134,6 +134,27 @@ export class Updater {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
process.noAsar = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot use app.relaunch(...) since it breaks privilege escalation on Linux...
|
||||||
|
private static relaunch(binPath: string) {
|
||||||
|
log4js.shutdown();
|
||||||
|
|
||||||
|
let proc;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// cwd is bugged on Windows, perhaps due to needing to be in system32 to launch cmd.exe
|
||||||
|
proc = cp.spawn(`"${binPath}"`, [], { stdio: 'ignore', shell: true, detached: true, windowsHide: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
proc = cp.spawn(binPath, [], { cwd: path.dirname(binPath), stdio: 'ignore', detached: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
proc.unref();
|
||||||
|
app.exit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static restart() {
|
public static restart() {
|
||||||
|
@ -142,15 +163,15 @@ export class Updater {
|
||||||
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
||||||
const updateBinPath = process.platform === 'darwin' ? path.join(updateInfo.tempPath, extractionDir) : path.join(updateInfo.tempPath, extractionDir, binaryName);
|
const updateBinPath = process.platform === 'darwin' ? path.join(updateInfo.tempPath, extractionDir) : path.join(updateInfo.tempPath, extractionDir, binaryName);
|
||||||
|
|
||||||
app.relaunch({ execPath: updateBinPath });
|
Updater.relaunch(updateBinPath);
|
||||||
app.exit();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isUpdating(): boolean {
|
public static isUpdating(): boolean {
|
||||||
try {
|
try {
|
||||||
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
||||||
Updater.updateApplied = updateInfo.updateState === 'cleanup' ? true : false;
|
// TODO: In case of error inform user
|
||||||
return true;
|
return updateInfo.updateState !== 'error';
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
return false;
|
return false;
|
||||||
|
@ -170,17 +191,17 @@ export class Updater {
|
||||||
try {
|
try {
|
||||||
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
||||||
const extractionDir = process.platform === 'darwin' ? 'FCast Receiver.app' : `fcast-receiver-${process.platform}-${process.arch}`;
|
const extractionDir = process.platform === 'darwin' ? 'FCast Receiver.app' : `fcast-receiver-${process.platform}-${process.arch}`;
|
||||||
|
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
||||||
|
const installBinPath = path.join(updateInfo.installPath, binaryName);
|
||||||
|
|
||||||
switch (updateInfo.updateState) {
|
switch (updateInfo.updateState) {
|
||||||
case UpdateState.Copy: {
|
case UpdateState.Copy: {
|
||||||
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info('Updater process started...');
|
logger.info('Updater process started...');
|
||||||
const src = path.join(updateInfo.tempPath, extractionDir);
|
const src = path.join(updateInfo.tempPath, extractionDir);
|
||||||
logger.info(`Copying files from update directory ${src} to install directory ${updateInfo.installPath}`);
|
logger.info(`Copying files from update directory ${src} to install directory ${updateInfo.installPath}`);
|
||||||
|
|
||||||
Updater.applyUpdate(src, updateInfo.installPath);
|
await Updater.applyUpdate(src, updateInfo.installPath);
|
||||||
updateInfo.updateState = UpdateState.Cleanup;
|
updateInfo.updateState = UpdateState.Cleanup;
|
||||||
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
||||||
|
|
||||||
|
@ -228,6 +249,7 @@ export class Updater {
|
||||||
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Updater.relaunch(installBinPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue