Skip to content

Commit eba52fc

Browse files
authored
Use owner-only permissions for IPC files (#1705)
* Use owner-only permissions for IPC files * Apply to session files * Fix linting errors * Update test
1 parent 4ca60c1 commit eba52fc

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

src/session.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export async function writeSessionFile(pid: string, pipePath: string) {
285285
await fs.ensureDir(sessionsDir);
286286
const filePath = path.join(sessionsDir, `${pid}.json`);
287287
await fs.writeJson(filePath, { pipe: pipePath });
288+
await setOwnerOnlyPermissions(filePath);
288289
}
289290

290291
async function updateActiveTerminalFiles(pipePath: string) {
@@ -299,6 +300,14 @@ async function updateActiveTerminalFiles(pipePath: string) {
299300
}
300301
}
301302

303+
async function setOwnerOnlyPermissions(filePath: string): Promise<void> {
304+
if (process.platform === 'win32') {
305+
return;
306+
}
307+
308+
await fs.chmod(filePath, 0o600);
309+
}
310+
302311
function makePipePath(): string {
303312
const suffix = crypto.randomBytes(8).toString('hex');
304313
if (process.platform === 'win32') {
@@ -381,10 +390,12 @@ export async function getGlobalPipePath(): Promise<string> {
381390
});
382391

383392
server.listen(pipePath, () => {
384-
globalPipePath = pipePath;
385-
globalSessionServer = server;
386-
console.info(`[SessionServer] Listening on ${pipePath}`);
387-
resolve(pipePath);
393+
void setOwnerOnlyPermissions(pipePath).then(() => {
394+
globalPipePath = pipePath;
395+
globalSessionServer = server;
396+
console.info(`[SessionServer] Listening on ${pipePath}`);
397+
resolve(pipePath);
398+
}).catch(reject);
388399
});
389400
});
390401
}
@@ -429,7 +440,8 @@ export async function getAttachSessionCommand(): Promise<string> {
429440
const sessPath = extensionContext.asAbsolutePath('sess').replace(/\\/g, '/');
430441
const installSessScriptPath = extensionContext.asAbsolutePath(path.join('R', 'install_sess.R')).replace(/\\/g, '/');
431442
const scriptPath = getAttachSessionScriptPath(pipePath);
432-
await fs.writeFile(scriptPath, buildAttachSessionScript(pipePath, sessPath, installSessScriptPath), { encoding: 'utf-8' });
443+
await fs.writeFile(scriptPath, buildAttachSessionScript(pipePath, sessPath, installSessScriptPath), { encoding: 'utf-8', mode: 0o600 });
444+
await setOwnerOnlyPermissions(scriptPath);
433445
attachSessionScriptPath = scriptPath;
434446

435447
return `source(${asRStringLiteral(scriptPath)})`;

src/test/suite/session.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import * as vscode from 'vscode';
22
import * as sinon from 'sinon';
33
import * as assert from 'assert';
44
import * as path from 'path';
5+
import * as os from 'os';
6+
import * as fs from 'fs-extra';
57

68
import { mockExtensionContext } from '../common/mockvscode';
79
import * as rTerminal from '../../rTerminal';
@@ -219,4 +221,37 @@ suite('Session Communication', () => {
219221
assert.ok(createWebviewPanelSpy.calledWith('webview'), 'webview should be triggered for html file');
220222

221223
}).timeout(45000);
224+
225+
test('attach session artifacts are owner-only', async () => {
226+
const command = await session.getAttachSessionCommand();
227+
const commandMatch = command.match(/^source\((.*)\)$/);
228+
if (!commandMatch) {
229+
throw new Error('attach command should be a source(...) call');
230+
}
231+
232+
const scriptPath = JSON.parse(commandMatch[1]) as string;
233+
const scriptStat = await fs.stat(scriptPath);
234+
if (process.platform !== 'win32') {
235+
assert.strictEqual(scriptStat.mode & 0o777, 0o600, 'attach script should be owner-only');
236+
}
237+
238+
const pipePath = session.globalPipePath;
239+
assert.ok(pipePath, 'global pipe path should be set');
240+
241+
if (pipePath && process.platform !== 'win32') {
242+
const pipeStat = await fs.stat(pipePath);
243+
assert.strictEqual(pipeStat.mode & 0o777, 0o600, 'socket file should be owner-only');
244+
}
245+
246+
const sessionFilePid = `perm-test-${process.pid}`;
247+
await session.writeSessionFile(sessionFilePid, pipePath ?? '');
248+
const sessionFilePath = path.join(os.homedir(), '.vscode-R', 'sessions', `${sessionFilePid}.json`);
249+
const sessionFileStat = await fs.stat(sessionFilePath);
250+
if (process.platform !== 'win32') {
251+
assert.strictEqual(sessionFileStat.mode & 0o777, 0o600, 'session handoff file should be owner-only');
252+
}
253+
await fs.remove(sessionFilePath);
254+
255+
await session.shutdownSessionWatcher();
256+
}).timeout(15000);
222257
});

0 commit comments

Comments
 (0)