Skip to content
Open
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
17 changes: 11 additions & 6 deletions launcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ char* join_executable_and_args(char *executable, char **args, int argc)

int run(int argc, char **argv, int is_gui) {

char python[256]; /* python executable's filename*/
char python[257]; /* python executable's filename*/
char *pyopt; /* Python option */
char script[256]; /* the script's filename */

Expand All @@ -256,7 +256,8 @@ int run(int argc, char **argv, int is_gui) {
char **newargs, **newargsp, **parsedargs; /* argument array for exec */
char *ptr, *end; /* working pointers for string manipulation */
char *cmdline;
int i, parsedargc; /* loop counter */
int i, parsedargc; /* loop counter */
int bytes_read;

/* compute script name from our .exe name*/
GetModuleFileNameA(NULL, script, sizeof(script));
Expand All @@ -282,13 +283,17 @@ int run(int argc, char **argv, int is_gui) {
if (scriptf == -1) {
return fail("Cannot open %s\n", script);
}
end = python + read(scriptf, python, sizeof(python));
bytes_read = read(scriptf, python, sizeof(python) - 1);
close(scriptf);
if (bytes_read < 0) {
return fail("Cannot read %s\n", script);
}
python[bytes_read] = '\0';

ptr = python-1;
while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
ptr = python;
while (*ptr && *ptr!='\n' && *ptr!='\r') ptr++;

*ptr-- = '\0';
*ptr = '\0';

if (strncmp(python, "#!", 2)) {
/* default to python.exe if no #! header */
Expand Down
1 change: 1 addition & 0 deletions newsfragments/+launcher-shebang-bounds.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prevent the Windows launcher from writing past its shebang buffer when the first 256 bytes contain no newline.
87 changes: 87 additions & 0 deletions setuptools/tests/test_windows_wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
are to wrap.
"""

import os
import pathlib
import platform
import shutil
import subprocess
import sys
import textwrap
Expand Down Expand Up @@ -256,3 +258,88 @@ def test_basic(self, tmpdir):
with (tmpdir / 'test_output.txt').open('rb') as f_out:
actual = f_out.read().decode('ascii')
assert actual == repr('Test Argument')


@pytest.fixture(scope='module')
def source_launcher(tmp_path_factory): # pragma: no cover
repo_root = pathlib.Path(__file__).parents[2]
build_dir = tmp_path_factory.mktemp('launcher-build')
if platform.machine() == 'ARM64':
cmake_architecture, runtime_architecture = 'ARM64', 'arm64'
elif sys.maxsize <= 2**32:
cmake_architecture, runtime_architecture = 'Win32', 'x86'
else:
cmake_architecture, runtime_architecture = 'x64', 'x64'
subprocess.run(
[
'cmake',
'-S',
str(repo_root / 'launcher'),
'-B',
str(build_dir),
'-A',
cmake_architecture,
'-DGUI=0',
'-DCMAKE_C_FLAGS_RELEASE=/O1 /fsanitize=address',
],
check=True,
)
subprocess.run(
['cmake', '--build', str(build_dir), '--config', 'Release'],
check=True,
)
vswhere = (
pathlib.Path(os.environ['ProgramFiles(x86)'])
/ 'Microsoft Visual Studio'
/ 'Installer'
/ 'vswhere.exe'
)
installation = pathlib.Path(
subprocess.check_output(
[
vswhere,
'-latest',
'-products',
'*',
'-property',
'installationPath',
],
text=True,
encoding='utf-8',
).strip()
)
toolset = (
(
installation
/ 'VC'
/ 'Auxiliary'
/ 'Build'
/ 'Microsoft.VCToolsVersion.default.txt'
)
.read_text(encoding='utf-8')
.strip()
)
runtime_by_name = {
path.name: path
for path in (installation / 'VC' / 'Tools' / 'MSVC' / toolset / 'bin').glob(
f'Host*/{runtime_architecture}/clang_rt.asan_dynamic-*.dll'
)
}
assert len(runtime_by_name) == 1
runtime = next(iter(runtime_by_name.values()))
return build_dir / 'Release' / 'launcher.exe', runtime


def test_maximum_shebang_stays_within_buffer( # pragma: no cover
tmp_path, source_launcher
):
launcher, sanitizer_runtime = source_launcher
wrapper = tmp_path / 'foo.exe'
shutil.copyfile(launcher, wrapper)
shutil.copyfile(sanitizer_runtime, tmp_path / sanitizer_runtime.name)

shebang = f'#!{subprocess.list2cmdline([sys.executable])}'.encode()
assert len(shebang) <= 256
(tmp_path / 'foo-script.py').write_bytes(shebang.ljust(256, b' '))

subprocess.run([wrapper], check=True, timeout=30)