CVE-2021-30502 (published 2021-04-25T03:15Z)

The unofficial vscode-ghc-simple (aka Simple Glasgow Haskell Compiler) extension before 0.2.3 for Visual Studio Code allows remote code execution via a crafted workspace configuration with replCommand.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/dramforever/vscode-ghc-simple
commit_details: repo_url: https://github.com/dramforever/vscode-ghc-simple
author: dramforever
author_date: 2021-04-24 23:10:44+08:00
msg: [bios] Add replCommand trust mechanism
repository: repo_url: https://github.com/dramforever/vscode-ghc-simple
repo_name: dramforever/vscode-ghc-simple
description: Simple GHC (Haskell) integration for VSCode
repo_language: TypeScript
owner: dramforever
file_changes: filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -144,6 +144,12 @@
                     "default": true,
                     "markdownDescription": "Whether GHCi sessions for workspaces linger after closing all associated documents.\n\nWhen enabled, the GHCi startup time is saved but takes more memory when idle. When disabled, kills GHCi after all associated files are closed, saves memory but takes time to restart. GHCi for a single file never linger."
                 },
+                "ghcSimple.trustedReplCommandConfigs": {
+                    "type": "object",
+                    "default": {},
+                    "scope": "application",
+                    "markdownDescription": "Using a `#ghcSimple.replCommand#` configuration from an untrusted workspace can pose a security risk. Whenever such a configuration occurs, you will be notified and if you choose to trust it, it will be recorded here."
+                },
                 "ghcSimple.startupCommands.all": {
                     "type": "array",
                     "items": {

programming_language: JavaScript
filename: config.ts
change_type: ModificationType.MODIFY
diff: @@ -89,12 +89,45 @@ async function singleConfig(cwd?: string): Promise<Configuration> {
     }
 }
 
+const alreadyShown = new Set();
+
+function handleReplCommandTrust(
+    workspaceUri: vscode.Uri,
+    replCommand: string
+): boolean {
+    if (workspaceUri.scheme !== 'file') return false;
+    const config = vscode.workspace.getConfiguration('ghcSimple', null);
+    const insp = config.inspect('trustedReplCommandConfigs').globalValue ?? {};
+    if (insp[workspaceUri.fsPath] === replCommand) {
+        return true;
+    } else {
+        if (! alreadyShown.has(workspaceUri.fsPath)) {
+            alreadyShown.add(workspaceUri.fsPath);
+            vscode.window.showWarningMessage(
+                `This workspace ${workspaceUri.fsPath} wants to run "${replCommand}" to start GHCi.\n\nAllow if you understand this and trust it.`,
+                'Allow', 'Ignore'
+            ).then((value) => {
+                alreadyShown.delete(workspaceUri.fsPath);
+                if (value == 'Allow') {
+                    const trusted = config.get('trustedReplCommandConfigs');
+                    trusted[workspaceUri.fsPath] = replCommand;
+                    config.update('trustedReplCommandConfigs', trusted, vscode.ConfigurationTarget.Global);
+                }
+            })
+        }
+        return false;
+    }
+}
+
 /** Configuration for a custom command */
 async function customConfig(
     replScope: 'workspace' | 'file',
     replCommand: string,
     workspaceUri: vscode.Uri
-): Promise<Configuration> {
+): Promise<Configuration | null> {
+    if (! handleReplCommandTrust(workspaceUri, replCommand))
+        return null;
+
     if (replCommand.indexOf('$stack_ide_targets') !== -1) {
         const sit = await getStackIdeTargets(workspaceUri);
         replCommand.replace(/\$stack_ide_targets/g, sit.join(' '));

programming_language: TypeScript