14893992bSMuhammad Usama Anjum#!/usr/bin/env python3 24893992bSMuhammad Usama Anjum# SPDX-License-Identifier: GPL-2.0 34893992bSMuhammad Usama Anjum# 44893992bSMuhammad Usama Anjum# Test that truncation of bprm->buf doesn't cause unexpected execs paths, along 54893992bSMuhammad Usama Anjum# with various other pathological cases. 64893992bSMuhammad Usama Anjumimport os, subprocess 74893992bSMuhammad Usama Anjum 84893992bSMuhammad Usama Anjum# Relevant commits 94893992bSMuhammad Usama Anjum# 104893992bSMuhammad Usama Anjum# b5372fe5dc84 ("exec: load_script: Do not exec truncated interpreter path") 114893992bSMuhammad Usama Anjum# 6eb3c3d0a52d ("exec: increase BINPRM_BUF_SIZE to 256") 124893992bSMuhammad Usama Anjum 134893992bSMuhammad Usama Anjum# BINPRM_BUF_SIZE 144893992bSMuhammad Usama AnjumSIZE=256 154893992bSMuhammad Usama Anjum 164893992bSMuhammad Usama AnjumNAME_MAX=int(subprocess.check_output(["getconf", "NAME_MAX", "."])) 174893992bSMuhammad Usama Anjum 184893992bSMuhammad Usama Anjumtest_num=0 19*99f5819bSMuhammad Usama Anjumpass_num=0 20*99f5819bSMuhammad Usama Anjumfail_num=0 214893992bSMuhammad Usama Anjum 224893992bSMuhammad Usama Anjumcode='''#!/usr/bin/perl 234893992bSMuhammad Usama Anjumprint "Executed interpreter! Args:\n"; 244893992bSMuhammad Usama Anjumprint "0 : '$0'\n"; 254893992bSMuhammad Usama Anjum$counter = 1; 264893992bSMuhammad Usama Anjumforeach my $a (@ARGV) { 274893992bSMuhammad Usama Anjum print "$counter : '$a'\n"; 284893992bSMuhammad Usama Anjum $counter++; 294893992bSMuhammad Usama Anjum} 304893992bSMuhammad Usama Anjum''' 314893992bSMuhammad Usama Anjum 324893992bSMuhammad Usama Anjum## 334893992bSMuhammad Usama Anjum# test - produce a binfmt_script hashbang line for testing 344893992bSMuhammad Usama Anjum# 354893992bSMuhammad Usama Anjum# @size: bytes for bprm->buf line, including hashbang but not newline 364893992bSMuhammad Usama Anjum# @good: whether this script is expected to execute correctly 374893992bSMuhammad Usama Anjum# @hashbang: the special 2 bytes for running binfmt_script 384893992bSMuhammad Usama Anjum# @leading: any leading whitespace before the executable path 394893992bSMuhammad Usama Anjum# @root: start of executable pathname 404893992bSMuhammad Usama Anjum# @target: end of executable pathname 414893992bSMuhammad Usama Anjum# @arg: bytes following the executable pathname 424893992bSMuhammad Usama Anjum# @fill: character to fill between @root and @target to reach @size bytes 434893992bSMuhammad Usama Anjum# @newline: character to use as newline, not counted towards @size 444893992bSMuhammad Usama Anjum# ... 454893992bSMuhammad Usama Anjumdef test(name, size, good=True, leading="", root="./", target="/perl", 464893992bSMuhammad Usama Anjum fill="A", arg="", newline="\n", hashbang="#!"): 47*99f5819bSMuhammad Usama Anjum global test_num, pass_num, fail_num, tests, NAME_MAX 484893992bSMuhammad Usama Anjum test_num += 1 494893992bSMuhammad Usama Anjum if test_num > tests: 504893992bSMuhammad Usama Anjum raise ValueError("more binfmt_script tests than expected! (want %d, expected %d)" 514893992bSMuhammad Usama Anjum % (test_num, tests)) 524893992bSMuhammad Usama Anjum 534893992bSMuhammad Usama Anjum middle = "" 544893992bSMuhammad Usama Anjum remaining = size - len(hashbang) - len(leading) - len(root) - len(target) - len(arg) 554893992bSMuhammad Usama Anjum # The middle of the pathname must not exceed NAME_MAX 564893992bSMuhammad Usama Anjum while remaining >= NAME_MAX: 574893992bSMuhammad Usama Anjum middle += fill * (NAME_MAX - 1) 584893992bSMuhammad Usama Anjum middle += '/' 594893992bSMuhammad Usama Anjum remaining -= NAME_MAX 604893992bSMuhammad Usama Anjum middle += fill * remaining 614893992bSMuhammad Usama Anjum 624893992bSMuhammad Usama Anjum dirpath = root + middle 634893992bSMuhammad Usama Anjum binary = dirpath + target 644893992bSMuhammad Usama Anjum if len(target): 654893992bSMuhammad Usama Anjum os.makedirs(dirpath, mode=0o755, exist_ok=True) 664893992bSMuhammad Usama Anjum open(binary, "w").write(code) 674893992bSMuhammad Usama Anjum os.chmod(binary, 0o755) 684893992bSMuhammad Usama Anjum 694893992bSMuhammad Usama Anjum buf=hashbang + leading + root + middle + target + arg + newline 704893992bSMuhammad Usama Anjum if len(newline) > 0: 714893992bSMuhammad Usama Anjum buf += 'echo this is not really perl\n' 724893992bSMuhammad Usama Anjum 734893992bSMuhammad Usama Anjum script = "binfmt_script-%s" % (name) 744893992bSMuhammad Usama Anjum open(script, "w").write(buf) 754893992bSMuhammad Usama Anjum os.chmod(script, 0o755) 764893992bSMuhammad Usama Anjum 774893992bSMuhammad Usama Anjum proc = subprocess.Popen(["./%s" % (script)], shell=True, 784893992bSMuhammad Usama Anjum stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 794893992bSMuhammad Usama Anjum stdout = proc.communicate()[0] 804893992bSMuhammad Usama Anjum 814893992bSMuhammad Usama Anjum if proc.returncode == 0 and b'Executed interpreter' in stdout: 824893992bSMuhammad Usama Anjum if good: 834893992bSMuhammad Usama Anjum print("ok %d - binfmt_script %s (successful good exec)" 844893992bSMuhammad Usama Anjum % (test_num, name)) 85*99f5819bSMuhammad Usama Anjum pass_num += 1 864893992bSMuhammad Usama Anjum else: 874893992bSMuhammad Usama Anjum print("not ok %d - binfmt_script %s succeeded when it should have failed" 884893992bSMuhammad Usama Anjum % (test_num, name)) 89*99f5819bSMuhammad Usama Anjum fail_num = 1 904893992bSMuhammad Usama Anjum else: 914893992bSMuhammad Usama Anjum if good: 924893992bSMuhammad Usama Anjum print("not ok %d - binfmt_script %s failed when it should have succeeded (rc:%d)" 934893992bSMuhammad Usama Anjum % (test_num, name, proc.returncode)) 94*99f5819bSMuhammad Usama Anjum fail_num = 1 954893992bSMuhammad Usama Anjum else: 964893992bSMuhammad Usama Anjum print("ok %d - binfmt_script %s (correctly failed bad exec)" 974893992bSMuhammad Usama Anjum % (test_num, name)) 98*99f5819bSMuhammad Usama Anjum pass_num += 1 994893992bSMuhammad Usama Anjum 1004893992bSMuhammad Usama Anjum # Clean up crazy binaries 1014893992bSMuhammad Usama Anjum os.unlink(script) 1024893992bSMuhammad Usama Anjum if len(target): 1034893992bSMuhammad Usama Anjum elements = binary.split('/') 1044893992bSMuhammad Usama Anjum os.unlink(binary) 1054893992bSMuhammad Usama Anjum elements.pop() 1064893992bSMuhammad Usama Anjum while len(elements) > 1: 1074893992bSMuhammad Usama Anjum os.rmdir("/".join(elements)) 1084893992bSMuhammad Usama Anjum elements.pop() 1094893992bSMuhammad Usama Anjum 1104893992bSMuhammad Usama Anjumtests=27 1114893992bSMuhammad Usama Anjumprint("TAP version 1.3") 1124893992bSMuhammad Usama Anjumprint("1..%d" % (tests)) 1134893992bSMuhammad Usama Anjum 1144893992bSMuhammad Usama Anjum### FAIL (8 tests) 1154893992bSMuhammad Usama Anjum 1164893992bSMuhammad Usama Anjum# Entire path is well past the BINFMT_BUF_SIZE. 1174893992bSMuhammad Usama Anjumtest(name="too-big", size=SIZE+80, good=False) 1184893992bSMuhammad Usama Anjum# Path is right at max size, making it impossible to tell if it was truncated. 1194893992bSMuhammad Usama Anjumtest(name="exact", size=SIZE, good=False) 1204893992bSMuhammad Usama Anjum# Same as above, but with leading whitespace. 1214893992bSMuhammad Usama Anjumtest(name="exact-space", size=SIZE, good=False, leading=" ") 1224893992bSMuhammad Usama Anjum# Huge buffer of only whitespace. 1234893992bSMuhammad Usama Anjumtest(name="whitespace-too-big", size=SIZE+71, good=False, root="", 1244893992bSMuhammad Usama Anjum fill=" ", target="") 1254893992bSMuhammad Usama Anjum# A good path, but it gets truncated due to leading whitespace. 1264893992bSMuhammad Usama Anjumtest(name="truncated", size=SIZE+17, good=False, leading=" " * 19) 1274893992bSMuhammad Usama Anjum# Entirely empty except for #! 1284893992bSMuhammad Usama Anjumtest(name="empty", size=2, good=False, root="", 1294893992bSMuhammad Usama Anjum fill="", target="", newline="") 1304893992bSMuhammad Usama Anjum# Within size, but entirely spaces 1314893992bSMuhammad Usama Anjumtest(name="spaces", size=SIZE-1, good=False, root="", fill=" ", 1324893992bSMuhammad Usama Anjum target="", newline="") 1334893992bSMuhammad Usama Anjum# Newline before binary. 1344893992bSMuhammad Usama Anjumtest(name="newline-prefix", size=SIZE-1, good=False, leading="\n", 1354893992bSMuhammad Usama Anjum root="", fill=" ", target="") 1364893992bSMuhammad Usama Anjum 1374893992bSMuhammad Usama Anjum### ok (19 tests) 1384893992bSMuhammad Usama Anjum 1394893992bSMuhammad Usama Anjum# The original test case that was broken by commit: 1404893992bSMuhammad Usama Anjum# 8099b047ecc4 ("exec: load_script: don't blindly truncate shebang string") 1414893992bSMuhammad Usama Anjumtest(name="test.pl", size=439, leading=" ", 1424893992bSMuhammad Usama Anjum root="./nix/store/bwav8kz8b3y471wjsybgzw84mrh4js9-perl-5.28.1/bin", 1434893992bSMuhammad Usama Anjum arg=" -I/nix/store/x6yyav38jgr924nkna62q3pkp0dgmzlx-perl5.28.1-File-Slurp-9999.25/lib/perl5/site_perl -I/nix/store/ha8v67sl8dac92r9z07vzr4gv1y9nwqz-perl5.28.1-Net-DBus-1.1.0/lib/perl5/site_perl -I/nix/store/dcrkvnjmwh69ljsvpbdjjdnqgwx90a9d-perl5.28.1-XML-Parser-2.44/lib/perl5/site_perl -I/nix/store/rmji88k2zz7h4zg97385bygcydrf2q8h-perl5.28.1-XML-Twig-3.52/lib/perl5/site_perl") 1444893992bSMuhammad Usama Anjum# One byte under size, leaving newline visible. 1454893992bSMuhammad Usama Anjumtest(name="one-under", size=SIZE-1) 1464893992bSMuhammad Usama Anjum# Two bytes under size, leaving newline visible. 1474893992bSMuhammad Usama Anjumtest(name="two-under", size=SIZE-2) 1484893992bSMuhammad Usama Anjum# Exact size, but trailing whitespace visible instead of newline 1494893992bSMuhammad Usama Anjumtest(name="exact-trunc-whitespace", size=SIZE, arg=" ") 1504893992bSMuhammad Usama Anjum# Exact size, but trailing space and first arg char visible instead of newline. 1514893992bSMuhammad Usama Anjumtest(name="exact-trunc-arg", size=SIZE, arg=" f") 1524893992bSMuhammad Usama Anjum# One bute under, with confirmed non-truncated arg since newline now visible. 1534893992bSMuhammad Usama Anjumtest(name="one-under-full-arg", size=SIZE-1, arg=" f") 1544893992bSMuhammad Usama Anjum# Short read buffer by one byte. 1554893992bSMuhammad Usama Anjumtest(name="one-under-no-nl", size=SIZE-1, newline="") 1564893992bSMuhammad Usama Anjum# Short read buffer by half buffer size. 1574893992bSMuhammad Usama Anjumtest(name="half-under-no-nl", size=int(SIZE/2), newline="") 1584893992bSMuhammad Usama Anjum# One byte under with whitespace arg. leaving wenline visible. 1594893992bSMuhammad Usama Anjumtest(name="one-under-trunc-arg", size=SIZE-1, arg=" ") 1604893992bSMuhammad Usama Anjum# One byte under with whitespace leading. leaving wenline visible. 1614893992bSMuhammad Usama Anjumtest(name="one-under-leading", size=SIZE-1, leading=" ") 1624893992bSMuhammad Usama Anjum# One byte under with whitespace leading and as arg. leaving newline visible. 1634893992bSMuhammad Usama Anjumtest(name="one-under-leading-trunc-arg", size=SIZE-1, leading=" ", arg=" ") 1644893992bSMuhammad Usama Anjum# Same as above, but with 2 bytes under 1654893992bSMuhammad Usama Anjumtest(name="two-under-no-nl", size=SIZE-2, newline="") 1664893992bSMuhammad Usama Anjumtest(name="two-under-trunc-arg", size=SIZE-2, arg=" ") 1674893992bSMuhammad Usama Anjumtest(name="two-under-leading", size=SIZE-2, leading=" ") 1684893992bSMuhammad Usama Anjumtest(name="two-under-leading-trunc-arg", size=SIZE-2, leading=" ", arg=" ") 1694893992bSMuhammad Usama Anjum# Same as above, but with buffer half filled 1704893992bSMuhammad Usama Anjumtest(name="two-under-no-nl", size=int(SIZE/2), newline="") 1714893992bSMuhammad Usama Anjumtest(name="two-under-trunc-arg", size=int(SIZE/2), arg=" ") 1724893992bSMuhammad Usama Anjumtest(name="two-under-leading", size=int(SIZE/2), leading=" ") 1734893992bSMuhammad Usama Anjumtest(name="two-under-lead-trunc-arg", size=int(SIZE/2), leading=" ", arg=" ") 1744893992bSMuhammad Usama Anjum 175*99f5819bSMuhammad Usama Anjumprint("# Totals: pass:%d fail:%d xfail:0 xpass:0 skip:0 error:0" % (pass_num, fail_num)) 176*99f5819bSMuhammad Usama Anjum 1774893992bSMuhammad Usama Anjumif test_num != tests: 1784893992bSMuhammad Usama Anjum raise ValueError("fewer binfmt_script tests than expected! (ran %d, expected %d" 1794893992bSMuhammad Usama Anjum % (test_num, tests)) 180