Skip to content

Commit c23f279

Browse files
authored
Merge pull request RustPython#2031 from BolunThompson/no_follow_symlink
Fixed bug where sys.executable may follow symlinks
2 parents 3cb018c + f754a40 commit c23f279

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

Lib/test/test_sys_executable.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import unittest
2+
import subprocess
3+
import sys
4+
import os
5+
6+
# These should go into a test_sys.py file, but given these are the only
7+
# ones being tested currently that seems unnecessary.
8+
9+
10+
class SysExecutableTest(unittest.TestCase):
11+
12+
# This is a copy of test.test_sys.SysModuleTest.test_executable from cpython.
13+
def cpython_tests(self):
14+
# sys.executable should be absolute
15+
self.assertEqual(os.path.abspath(sys.executable), sys.executable)
16+
17+
# Issue #7774: Ensure that sys.executable is an empty string if argv[0]
18+
# has been set to a non existent program name and Python is unable to
19+
# retrieve the real program name
20+
21+
# For a normal installation, it should work without 'cwd'
22+
# argument. For test runs in the build directory, see #7774.
23+
python_dir = os.path.dirname(os.path.realpath(sys.executable))
24+
p = subprocess.Popen(
25+
[
26+
"nonexistent",
27+
"-c",
28+
'import sys; print(sys.executable.encode("ascii", "backslashreplace"))',
29+
],
30+
executable=sys.executable,
31+
stdout=subprocess.PIPE,
32+
cwd=python_dir,
33+
)
34+
stdout = p.communicate()[0]
35+
executable = stdout.strip().decode("ASCII")
36+
p.wait()
37+
self.assertIn(
38+
executable,
39+
["b''", repr(sys.executable.encode("ascii", "backslashreplace"))],
40+
)
41+
42+
def test_no_follow_symlink(self):
43+
paths = [os.path.abspath("test_symlink"), "./test_symlink"]
44+
for path in paths:
45+
with self.subTest(path=path):
46+
os.symlink(sys.executable, path)
47+
command = [
48+
path,
49+
"-c",
50+
"import sys; print(sys.executable, end='')",
51+
]
52+
try:
53+
process = subprocess.run(command, capture_output=True)
54+
finally:
55+
os.remove(path)
56+
self.assertEqual(
57+
os.path.abspath(path), process.stdout.decode("utf-8")
58+
)
59+
60+
61+
if __name__ == "__main__":
62+
unittest.main()

vm/src/sysmodule.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{env, mem};
1+
use std::{env, mem, path};
22

33
use crate::builtins;
44
use crate::frame::FrameRef;
@@ -28,9 +28,18 @@ fn argv(vm: &VirtualMachine) -> PyObjectRef {
2828
}
2929

3030
fn executable(ctx: &PyContext) -> PyObjectRef {
31-
if let Ok(path) = env::current_exe() {
32-
if let Ok(path) = path.into_os_string().into_string() {
33-
return ctx.new_str(path);
31+
if let Some(exec_path) = env::args().next() {
32+
if path::Path::new(&exec_path).is_absolute() {
33+
return ctx.new_str(exec_path);
34+
}
35+
if let Ok(dir) = env::current_dir() {
36+
if let Ok(dir) = dir.into_os_string().into_string() {
37+
return ctx.new_str(format!(
38+
"{}/{}",
39+
dir,
40+
exec_path.strip_prefix("./").unwrap_or(&exec_path)
41+
));
42+
}
3443
}
3544
}
3645
ctx.none()

0 commit comments

Comments
 (0)