Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Notable changes.

## February 2025

### [0.74.0]
- Ignore non-writeable HOME (https://github.com/microsoft/vscode-remote-release/issues/10707)

## January 2025

### [0.73.0]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devcontainers/cli",
"description": "Dev Containers CLI",
"version": "0.73.0",
"version": "0.74.0",
"bin": {
"devcontainer": "devcontainer.js"
},
Expand Down
17 changes: 14 additions & 3 deletions src/spec-common/injectHeadless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export async function getContainerProperties(options: {
params.output.write(toWarningText(`User ${containerUser} not found with 'getent passwd'.`));
}
const shell = await getUserShell(containerEnv, passwdUser);
const homeFolder = await getHomeFolder(containerEnv, passwdUser);
const homeFolder = await getHomeFolder(shellServer, containerEnv, passwdUser);
const userDataFolder = getUserDataFolder(homeFolder, params);
let rootShellServerP: Promise<ShellServer> | undefined;
if (rootShellServer) {
Expand Down Expand Up @@ -278,8 +278,19 @@ export async function getUser(shellServer: ShellServer) {
return (await shellServer.exec('id -un')).stdout.trim();
}

export async function getHomeFolder(containerEnv: NodeJS.ProcessEnv, passwdUser: PasswdUser | undefined) {
return containerEnv.HOME || (passwdUser && passwdUser.home) || '/root';
export async function getHomeFolder(shellServer: ShellServer, containerEnv: NodeJS.ProcessEnv, passwdUser: PasswdUser | undefined) {
if (containerEnv.HOME) {
if (containerEnv.HOME === passwdUser?.home || passwdUser?.uid === '0') {
return containerEnv.HOME;
}
try {
await shellServer.exec(`[ ! -e '${containerEnv.HOME}' ] || [ -w '${containerEnv.HOME}' ]`);
return containerEnv.HOME;
} catch {
// Exists but not writable.
}
}
return passwdUser?.home || '/root';
}

async function getUserShell(containerEnv: NodeJS.ProcessEnv, passwdUser: PasswdUser | undefined) {
Expand Down
46 changes: 46 additions & 0 deletions src/test/getHomeFolder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { shellExec, output } from './testUtils';
import { dockerExecFunction } from '../spec-shutdown/dockerUtils';
import { plainExec } from '../spec-common/commonUtils';
import { launch } from '../spec-common/shellServer';
import { getHomeFolder, getUserFromPasswdDB } from '../spec-common/injectHeadless';

describe('getHomeFolder', function () {
this.timeout('20s');

it(`should ignore non-writeable HOME`, async () => {
const res = await shellExec(`docker run -d mcr.microsoft.com/devcontainers/base:latest sleep inf`);
const containerId = res.stdout.trim();

const vscodeShellServer = await launchShellServer(containerId, 'vscode');
const vscodeUser = await getUserFromPasswdDB(vscodeShellServer, 'vscode');
assert.ok(vscodeUser);

assert.strictEqual(await getHomeFolder(vscodeShellServer, {}, vscodeUser), '/home/vscode');
assert.strictEqual(await getHomeFolder(vscodeShellServer, { HOME: '/root' }, vscodeUser), '/home/vscode');
assert.strictEqual(await getHomeFolder(vscodeShellServer, { HOME: '/home/vscode' }, vscodeUser), '/home/vscode');
assert.strictEqual(await getHomeFolder(vscodeShellServer, { HOME: '/home/vscode/foo' }, vscodeUser), '/home/vscode/foo');

const rootServer = await launchShellServer(containerId, 'root');
const rootUser = await getUserFromPasswdDB(rootServer, 'root');
assert.ok(rootUser);

assert.strictEqual(await getHomeFolder(rootServer, {}, rootUser), '/root');
assert.strictEqual(await getHomeFolder(rootServer, { HOME: '/home/vscode' }, rootUser), '/home/vscode');
});

async function launchShellServer(containerId: string, username: string) {
const exec = dockerExecFunction({
exec: plainExec(undefined),
cmd: 'docker',
env: {},
output,
}, containerId, username);
return launch(exec, output);
}
});