Skip to content

Commit 3972f73

Browse files
committed
[security] [dart:io] Fix current directory being in front of PATH.
This is a security improvement. On Linux and Android, starting a process with Process.run, Process.runSync or Process.start would first search the current directory before searching PATH (Issue [37101][]). Operating systems other than Linux and Android didn't have this behavior and aren't affected by this vulnerability. Effectively this puts the current working directory in the front of PATH, even if it wasn't in the PATH. This change fixes that vulnerability and only searches the directories in the PATH environment variable. Fixes #37101 Change-Id: I05f3137753237f9b3ba4be4eba63ad07a75d865e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105582 Reviewed-by: William Hesse <[email protected]>
1 parent a356f64 commit 3972f73

3 files changed

Lines changed: 47 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
(Add new changes here, and they will be copied to the change section for the
33
next dev version)
44

5+
### Security vulnerability
6+
7+
* **Security improvement:** On Linux and Android, starting a process with
8+
`Process.run`, `Process.runSync`, or `Process.start` would first search the
9+
current directory before searching `PATH` (Issue [37101][]). This behavior
10+
effectively put the current working directory in the front of `PATH`, even if
11+
it wasn't in the `PATH`. This release changes that behavior to only searching
12+
the directories in the `PATH` environment variable. Operating systems other
13+
than Linux and Android didn't have this behavior and aren't affected by this
14+
vulnerability.
15+
16+
This vulnerability could result in execution of untrusted code if a command
17+
without a slash in its name was run inside an untrusted directory containing
18+
an executable file with that name:
19+
20+
```dart
21+
Process.run("ls", workingDirectory: "/untrusted/directory")
22+
```
23+
24+
This would attempt to run `/untrusted/directory/ls` if it existed, even
25+
though it is not in the `PATH`. It was always safe to instead use an absolute
26+
path or a path containing a slash.
27+
28+
This vulnerability was introduced in Dart 2.0.0.
29+
30+
[37101]: https://github.com/dart-lang/sdk/issues/37101
31+
532
### Core libraries
633

734
#### `dart:isolate`

runtime/bin/process_android.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -436,24 +436,24 @@ class ProcessStarter {
436436
}
437437
}
438438

439-
// Tries to find path_ relative to the current namespace.
439+
// Tries to find path_ relative to the current namespace unless it should be
440+
// searched in the PATH.
440441
// The path that should be passed to exec is returned in realpath.
441442
// Returns true on success, and false if there was an error that should
442443
// be reported to the parent.
443444
bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
445+
// Perform a PATH search if there's no slash in the path.
446+
if (strchr(path_, '/') == NULL) {
447+
// TODO(zra): If there is a non-default namespace, the entries in PATH
448+
// should be treated as relative to the namespace.
449+
strncpy(realpath, path_, realpath_size);
450+
realpath[realpath_size - 1] = '\0';
451+
return true;
452+
}
444453
NamespaceScope ns(namespc_, path_);
445454
const int fd =
446455
TEMP_FAILURE_RETRY(openat(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
447456
if (fd == -1) {
448-
if ((errno == ENOENT) && (strchr(path_, '/') == NULL)) {
449-
// path_ was not found relative to the namespace, but since it didn't
450-
// contain a '/', we can pass it directly to execvp, which will do a
451-
// lookup in PATH.
452-
// TODO(zra): If there is a non-default namespace, the entries in PATH
453-
// should be treated as relative to the namespace.
454-
strncpy(realpath, path_, realpath_size);
455-
return true;
456-
}
457457
return false;
458458
}
459459
char procpath[PATH_MAX];

runtime/bin/process_linux.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -436,24 +436,24 @@ class ProcessStarter {
436436
}
437437
}
438438

439-
// Tries to find path_ relative to the current namespace.
439+
// Tries to find path_ relative to the current namespace unless it should be
440+
// searched in the PATH.
440441
// The path that should be passed to exec is returned in realpath.
441442
// Returns true on success, and false if there was an error that should
442443
// be reported to the parent.
443444
bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
445+
// Perform a PATH search if there's no slash in the path.
446+
if (strchr(path_, '/') == NULL) {
447+
// TODO(zra): If there is a non-default namespace, the entries in PATH
448+
// should be treated as relative to the namespace.
449+
strncpy(realpath, path_, realpath_size);
450+
realpath[realpath_size - 1] = '\0';
451+
return true;
452+
}
444453
NamespaceScope ns(namespc_, path_);
445454
const int fd =
446455
TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
447456
if (fd == -1) {
448-
if ((errno == ENOENT) && (strchr(path_, '/') == NULL)) {
449-
// path_ was not found relative to the namespace, but since it didn't
450-
// contain a '/', we can pass it directly to execvp, which will do a
451-
// lookup in PATH.
452-
// TODO(zra): If there is a non-default namespace, the entries in PATH
453-
// should be treated as relative to the namespace.
454-
strncpy(realpath, path_, realpath_size);
455-
return true;
456-
}
457457
return false;
458458
}
459459
char procpath[PATH_MAX];

0 commit comments

Comments
 (0)