Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Use mkdtemp for secure temp files #1912

Merged
merged 6 commits into from
Sep 13, 2018

Conversation

0xch4z
Copy link
Contributor

@0xch4z 0xch4z commented Sep 4, 2018

Closes #1905.

This closely mirrors the official TypeScript extension's implementation, which does not persist the directory path for reuse between activations.

@ramya-rao-a, making the directory path persistent is pretty hacky and not the convention for any of the official extensions I've seen, so I've left it out (let me know your thoughts on this).

@msftclas
Copy link

msftclas commented Sep 4, 2018

CLA assistant check
All CLA requirements met.

Copy link
Contributor

@ramya-rao-a ramya-rao-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @Charliekenney23!

I would still insist on persisting the tmp dir path. Otherwise, we would end up creating new directories unnecessarily each time the user saves a file or runs code coverage.

src/goBuild.ts Outdated
@@ -70,7 +70,7 @@ export function goBuild(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigura
}

const buildEnv = Object.assign({}, getToolsEnvVars());
const tmpPath = path.normalize(path.join(os.tmpdir(), 'go-code-check.' + getUserNameHash()));
const tmpPath = getTempFile('go-code-check.' + getUserNameHash());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can skip the use of user name hash here which was introduced to fix #1829
The current solution of using fs.mkdtempSync should fix #1829 as well.

src/util.ts Outdated
let dir: string | undefined;
return (): string => {
if (!dir) {
dir = fs.mkdtempSync(os.tmpdir() + '/vscode-go');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use path.sep to create the path here instead of /

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 10, 2018

@ramya-rao-a I've updated the code to persist the temp directory path between vscode sessions. Please let me know if there's anything else that needs to be updated!

@0xch4z 0xch4z force-pushed the secure-tmp-files branch 2 times, most recently from ff41b84 to 1664a15 Compare September 10, 2018 17:28
@@ -12,6 +12,7 @@ import { basename, dirname, extname } from 'path';
import { spawn, ChildProcess, execSync, spawnSync, execFile } from 'child_process';
import { Client, RPCConnection } from 'json-rpc2';
import { parseEnvFile, getBinPathWithPreferredGopath, resolveHomeDir, getInferredGopath, getCurrentGoWorkspaceFromGOPATH, envPath, fixDriveCasingInWindows } from '../goPath';
import { TempFileProvider } from '../util';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug adapter runs in a separate process and does not have access to anything from the vscode module. Therefore a dependency here on util will fail as util imports from vscode.

The tmp dir is used in the debug adapter to create the trace files. Since this is not done by default and the user sets explicitly to debug an issue with the debugger, I believe we are ok with creating a new file under os.tmpdir()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

src/util.ts Outdated
private constructor() {}
private static _instance: TempFileProvider;

static get instance() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this a static class? Then, we can skip the instance() method here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point. I'll make this a static class 😃

src/util.ts Outdated
return TempFileProvider._instance;
}

private store: vscode.Memento;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call this the globalState so that when code reading we know exactly where the data is stored

src/util.ts Outdated
this.store = store;
}

get dir(): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since getFilePath is the only place this is used, can we merge this code into getFilePath?

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 10, 2018

I've updated the code with the suggested changes 😃

src/goMain.ts Outdated
@@ -83,6 +83,7 @@ export function activate(ctx: vscode.ExtensionContext): void {
});
}
ctx.globalState.update('goroot', currentGoroot);
TempFileProvider.registerStore(ctx.globalState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the very top. Just to make sure that the globalState is available in the TempFileProvider right from the start

src/util.ts Outdated
* returns path to temp file with name
*/
static getFilePath(name: string): string {
let tempDir = TempFileProvider.globalState.get<string>('tempDir');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One corner case is that getFilePath gets called when the globalState is not yet set. This may not happen now, but when the code that registers the store gets moved around in the future, it can happen.

Copy link
Contributor Author

@0xch4z 0xch4z Sep 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment to above TempFileProvider.registerStore must be called before this.

One work around could be creating a new temp dir if globalState is undefined (and storing it locally on the object). Thoughts?

@0xch4z 0xch4z force-pushed the secure-tmp-files branch 2 times, most recently from fc2cdfd to 10757de Compare September 10, 2018 18:21
@antong
Copy link

antong commented Sep 10, 2018

Many systems clean the tmp directory every boot or on other schedules or even use a RAM based file system that is not persisted to disk. Will the persisted tmpdir in this PR work correctly if the tmpdir disappears?

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 10, 2018

@antong yes. If you have a look here, I'm checking if the persisted directory exists and creating it if not present.

@antong
Copy link

antong commented Sep 10, 2018

Usually, mkdtemp and friends are used so that the library safely creates and picks a directory and then you use whatever dir you got. I haven't seen it used so that you recreate one with the same name you got earlier. It may be ok, but I'm not sure.

Now, you may call me overly paranoid, but what about the following attack scenario: Alice uses the vscode extension, which correctly makes a tmpdir /tmp/vscode.hMF460T1CB and it is persisted in vscode's config. Bob on the same machine monitors /tmp and sees Alice's tmpdir. It is inaccessible to him, so he can't do anything about it. However, Bob knows that /tmp is wiped on boot, and he crafts a cron job (a scheduled program execution) that as soon as possible (when the dir disappears) creates a directory with Alice's tmpdir name and sets the permissions to be accessible by Alice (e.g., world readable and writable) and performs the symlink attack I suggested earlier, e.g., ln -s /home/alice/thesis.tex /tmp/vscode.hMF460T1CB/go-code-cover. Alice then opens up vscode, the correctly named dir exists, so the vscode extension reuses it and her thesis.tex is overwritten.

Personally I'd err on the side of caution and not persist the tmpdir name, but recreate it every time and remove it when done with. If the concern is that junk may be left behind, we could remove all /tmp/vscode.* owned by the user at some convenient time, perhaps before creating a new tmpdir. If we want a persistent dir, then tmpdir might not be the right thing and we should put it under the users home dir or maybe the workspace dir.

Sorry for trying to figure out how to break things, I've my security goggles on this week :-)

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 10, 2018

So I think the only benefit of persisting the temp directory path is to limit VSCode from polluting the /tmp directory (like with microsoft/vscode#53423). While the scenario that you walked through would be extreme, I agree that creating a directory at launch and cleaning up in deactivate would solve the problem and without any extra risk in the first place. Thanks for weighing in.

Thoughts, @ramya-rao-a?

@ramya-rao-a
Copy link
Contributor

Those are some great points @antong, thanks for bringing them up.

I have pushed a change to not re-use a persisted directory if it does not exist. That should take care of most of your concerns

@Charliekenney23 The comment is helpful, but I added a check regardless. Do take a look.

creating a directory at launch and cleaning up in deactivate would solve the problem and without any extra risk in the first place.

Yes, lets do this if @antong finds any other risk with the change I made :)

@antong
Copy link

antong commented Sep 11, 2018

What if the directory does exist and is writable, but is owned by somebody else (was autoremoved, but recreated by evil Bob)?

@ramya-rao-a
Copy link
Contributor

good point, lets go with creating a tmpdir on extension activation (which happens for each VS Code session), recreating it if it doesnt exist during the session and removing it when extension is deactivated (which happens when VS Code window is closed)

@ramya-rao-a
Copy link
Contributor

On a side note: @antong you are awesome

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 11, 2018

Agreed! I've just updated the code.

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 12, 2018

Just a note: There will be some edge cases where the tmp dir will not be deleted (for instance, when the user force quits vscode, the extension does not have time to call deactivate and delete the temp dir). Though, any stray temp dirs will not be reused.

In effort to minimize these stray directories, I'm only creating the temp directory as needed (when the TempFileProvider.getFilePath method is first called).

@ramya-rao-a
Copy link
Contributor

@Charliekenney23 I've refactored the code a bit to avoid using classes as the rest of the util.ts file doesn't use any. This is just to be consistent with the coding style in the file and to make it easier for future maintenance. Can you take a look?

@0xch4z
Copy link
Contributor Author

0xch4z commented Sep 13, 2018

@ramya-rao-a LGTM!

@ramya-rao-a ramya-rao-a merged commit 7885423 into microsoft:master Sep 13, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Insecure use of temporary file
4 participants