Skip to content

Commit

Permalink
fix potential sandbox escape via custom vencordDir
Browse files Browse the repository at this point in the history
  • Loading branch information
Vendicated committed Jul 4, 2024
1 parent ec3d83f commit 1f12d27
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 20 deletions.
6 changes: 4 additions & 2 deletions src/main/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
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_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
Expand All @@ -47,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
// needs to be inline require because of circular dependency
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
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)`;

Expand Down
16 changes: 13 additions & 3 deletions src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
import { Settings, State } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout";
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
Expand Down Expand Up @@ -105,7 +105,15 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
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!, {
properties: ["openDirectory"]
});
Expand All @@ -114,7 +122,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const dir = res.filePaths[0];
if (!isValidVencordInstall(dir)) return "invalid";

return dir;
State.store.vencordDir = dir;

return "ok";
});

handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
Expand Down
15 changes: 11 additions & 4 deletions src/main/utils/vencordLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* 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 { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
Expand Down Expand Up @@ -56,12 +57,18 @@ export async function downloadVencordFiles() {
);
}

export function isValidVencordInstall(dir: string) {
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
const existsAsync = (path: string) =>
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() {
if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;

mkdirSync(VENCORD_FILES_DIR, { recursive: true });

await downloadVencordFiles();
Expand Down
4 changes: 2 additions & 2 deletions src/preload/VesktopNative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { Node } from "@vencord/venmic";
import { ipcRenderer } from "electron";
import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest";

import { IpcEvents } from "../shared/IpcEvents";
import { invoke, sendSync } from "./typedIpc";
Expand All @@ -34,7 +33,8 @@ export const VesktopNative = {
},
fileManager: {
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: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
Expand Down
28 changes: 21 additions & 7 deletions src/renderer/components/settings/VencordLocationPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/

import { useForceUpdater } from "@vencord/types/utils";
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";

import { SettingsComponent } from "./Settings";

export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const forceUpdate = useForceUpdater();
const vencordDir = VesktopNative.fileManager.getVencordDir();

return (
<>
<Forms.FormText>
Vencord files are loaded from{" "}
{settings.vencordDir ? (
{vencordDir ? (
<a
href="about:blank"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!);
VesktopNative.fileManager.showItemInFolder(vencordDir!);
}}
>
{settings.vencordDir}
{vencordDir}
</a>
) : (
"the default location"
Expand All @@ -34,25 +38,35 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
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":
Toasts.show({
message:
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});
return;
break;
}
settings.vencordDir = choice;
forceUpdate();
}}
>
Change
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
onClick={() => (settings.vencordDir = void 0)}
onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate();
}}
>
Reset
</Button>
Expand Down
18 changes: 17 additions & 1 deletion src/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ console.log("read if cute :3");

export * as Components from "./components";
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 { Settings } from "./settings";
Expand Down Expand Up @@ -59,3 +59,19 @@ VesktopNative.arrpc.onActivity(async 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
)
);
}
1 change: 1 addition & 0 deletions src/shared/IpcEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const enum IpcEvents {
GET_SETTINGS = "VCD_GET_SETTINGS",
SET_SETTINGS = "VCD_SET_SETTINGS",

GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",

UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
Expand Down
3 changes: 2 additions & 1 deletion src/shared/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type { Rectangle } from "electron";

export interface Settings {
discordBranch?: "stable" | "canary" | "ptb";
vencordDir?: string;
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
tray?: boolean;
minimizeToTray?: boolean;
Expand Down Expand Up @@ -54,4 +53,6 @@ export interface State {
firstLaunch?: boolean;

steamOSLayoutVersion?: number;

vencordDir?: string;
}
13 changes: 13 additions & 0 deletions src/shared/utils/SettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ export class SettingsStore<T extends object> {
self.pathListeners.get(setPath)?.forEach(cb => cb(value));

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;
}
});
}
Expand Down

0 comments on commit 1f12d27

Please sign in to comment.