fix potential sandbox escape via custom vencordDir

This commit is contained in:
Vendicated 2024-07-04 18:40:04 +02:00
parent ec3d83f7ca
commit 1f12d270ec
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
9 changed files with 84 additions and 20 deletions

View file

@ -37,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
console.error("Migration failed", e); console.error("Migration failed", e);
} }
} }
app.setPath("sessionData", join(DATA_DIR, "sessionData")); const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
app.setPath("sessionData", SESSION_DATA_DIR);
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
@ -47,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
// needs to be inline require because of circular dependency // needs to be inline require because of circular dependency
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised // as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
export const VENCORD_FILES_DIR = export const VENCORD_FILES_DIR =
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist"); (require("./settings") as typeof import("./settings")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`; export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;

View file

@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
import { autoStart } from "./autoStart"; import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
import { mainWin } from "./mainWindow"; import { mainWin } from "./mainWindow";
import { Settings } from "./settings"; import { Settings, State } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers"; import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout"; import { PopoutWindows } from "./utils/popout";
import { isDeckGameMode, showGamePage } from "./utils/steamOS"; import { isDeckGameMode, showGamePage } from "./utils/steamOS";
@ -105,7 +105,15 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
e.sender.session.addWordToSpellCheckerDictionary(word); e.sender.session.addWordToSpellCheckerDictionary(word);
}); });
handle(IpcEvents.SELECT_VENCORD_DIR, async () => { handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
console.log(value);
if (value === null) {
delete State.store.vencordDir;
return "ok";
}
const res = await dialog.showOpenDialog(mainWin!, { const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openDirectory"] properties: ["openDirectory"]
}); });
@ -114,7 +122,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const dir = res.filePaths[0]; const dir = res.filePaths[0];
if (!isValidVencordInstall(dir)) return "invalid"; if (!isValidVencordInstall(dir)) return "invalid";
return dir; State.store.vencordDir = dir;
return "ok";
}); });
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));

View file

@ -4,7 +4,8 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { existsSync, mkdirSync } from "fs"; import { mkdirSync } from "fs";
import { access, constants as FsConstants } from "fs/promises";
import { join } from "path"; import { join } from "path";
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
@ -56,12 +57,18 @@ export async function downloadVencordFiles() {
); );
} }
export function isValidVencordInstall(dir: string) { const existsAsync = (path: string) =>
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f))); access(path, FsConstants.F_OK)
.then(() => true)
.catch(() => false);
export async function isValidVencordInstall(dir: string) {
return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
} }
export async function ensureVencordFiles() { export async function ensureVencordFiles() {
if (isValidVencordInstall(VENCORD_FILES_DIR)) return; if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
mkdirSync(VENCORD_FILES_DIR, { recursive: true }); mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles(); await downloadVencordFiles();

View file

@ -7,7 +7,6 @@
import { Node } from "@vencord/venmic"; import { Node } from "@vencord/venmic";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import type { Settings } from "shared/settings"; import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest";
import { IpcEvents } from "../shared/IpcEvents"; import { IpcEvents } from "../shared/IpcEvents";
import { invoke, sendSync } from "./typedIpc"; import { invoke, sendSync } from "./typedIpc";
@ -34,7 +33,8 @@ export const VesktopNative = {
}, },
fileManager: { fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path), showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR) getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
}, },
settings: { settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS), get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),

View file

@ -4,24 +4,28 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { useForceUpdater } from "@vencord/types/utils";
import { Button, Forms, Toasts } from "@vencord/types/webpack/common"; import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings"; import { SettingsComponent } from "./Settings";
export const VencordLocationPicker: SettingsComponent = ({ settings }) => { export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const forceUpdate = useForceUpdater();
const vencordDir = VesktopNative.fileManager.getVencordDir();
return ( return (
<> <>
<Forms.FormText> <Forms.FormText>
Vencord files are loaded from{" "} Vencord files are loaded from{" "}
{settings.vencordDir ? ( {vencordDir ? (
<a <a
href="about:blank" href="about:blank"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!); VesktopNative.fileManager.showItemInFolder(vencordDir!);
}} }}
> >
{settings.vencordDir} {vencordDir}
</a> </a>
) : ( ) : (
"the default location" "the default location"
@ -34,7 +38,14 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const choice = await VesktopNative.fileManager.selectVencordDir(); const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) { switch (choice) {
case "cancelled": case "cancelled":
return; break;
case "ok":
Toasts.show({
message: "Vencord install changed. Fully restart Vesktop to apply.",
id: Toasts.genId(),
type: Toasts.Type.SUCCESS
});
break;
case "invalid": case "invalid":
Toasts.show({ Toasts.show({
message: message:
@ -42,9 +53,9 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
id: Toasts.genId(), id: Toasts.genId(),
type: Toasts.Type.FAILURE type: Toasts.Type.FAILURE
}); });
return; break;
} }
settings.vencordDir = choice; forceUpdate();
}} }}
> >
Change Change
@ -52,7 +63,10 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
<Button <Button
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
color={Button.Colors.RED} color={Button.Colors.RED}
onClick={() => (settings.vencordDir = void 0)} onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate();
}}
> >
Reset Reset
</Button> </Button>

View file

@ -13,7 +13,7 @@ console.log("read if cute :3");
export * as Components from "./components"; export * as Components from "./components";
import { findByPropsLazy, onceReady } from "@vencord/types/webpack"; import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common"; import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/settings/Settings"; import SettingsUi from "./components/settings/Settings";
import { Settings } from "./settings"; import { Settings } from "./settings";
@ -59,3 +59,19 @@ VesktopNative.arrpc.onActivity(async data => {
arRPC.handleEvent(new MessageEvent("message", { data })); arRPC.handleEvent(new MessageEvent("message", { data }));
}); });
// TODO: remove soon
const vencordDir = "vencordDir" as keyof typeof Settings.store;
if (Settings.store[vencordDir]) {
onceReady.then(() =>
setTimeout(
() =>
Alerts.show({
title: "Custom Vencord Location",
body: "Due to changes in Vesktop, your custom Vencord location had to bee reset. Please set it again in the settings.",
onConfirm: () => delete Settings.store[vencordDir]
}),
5000
)
);
}

View file

@ -23,6 +23,7 @@ export const enum IpcEvents {
GET_SETTINGS = "VCD_GET_SETTINGS", GET_SETTINGS = "VCD_GET_SETTINGS",
SET_SETTINGS = "VCD_SET_SETTINGS", SET_SETTINGS = "VCD_SET_SETTINGS",
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR", SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA", UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",

View file

@ -8,7 +8,6 @@ import type { Rectangle } from "electron";
export interface Settings { export interface Settings {
discordBranch?: "stable" | "canary" | "ptb"; discordBranch?: "stable" | "canary" | "ptb";
vencordDir?: string;
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic"; transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
tray?: boolean; tray?: boolean;
minimizeToTray?: boolean; minimizeToTray?: boolean;
@ -54,4 +53,6 @@ export interface State {
firstLaunch?: boolean; firstLaunch?: boolean;
steamOSLayoutVersion?: number; steamOSLayoutVersion?: number;
vencordDir?: string;
} }

View file

@ -59,6 +59,19 @@ export class SettingsStore<T extends object> {
self.pathListeners.get(setPath)?.forEach(cb => cb(value)); self.pathListeners.get(setPath)?.forEach(cb => cb(value));
return true; return true;
},
deleteProperty(target, key: string) {
if (!(key in target)) return true;
const res = Reflect.deleteProperty(target, key);
if (!res) return false;
const setPath = `${path}${path && "."}${key}`;
self.globalListeners.forEach(cb => cb(root, setPath));
self.pathListeners.get(setPath)?.forEach(cb => cb(undefined));
return res;
} }
}); });
} }