import sys, os, re, tempfile, shutil
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'tests'))
from security_framework import SecurityTestFramework
config = {
'tool_name': 'shred',
'bin_name': 'fshred',
'gnu_path': '/usr/bin/shred',
'bss_size': 65536,
'max_binary_size': 100000,
'test_args': ['-n', '1', '/dev/null'],
'test_stdin': None,
'timeout': 10,
}
def tool_specific_tests(fw):
fw.log("\n=== Shred-Specific Tests ===")
tmpdir = tempfile.mkdtemp()
try:
test_file = os.path.join(tmpdir, "basic.dat")
with open(test_file, "wb") as f:
f.write(b"test data\n")
rc, _, _ = fw.run_asm(["-n", "1", test_file])
fw.report_result(rc == 0, "shred: basic shred completes")
large_file = os.path.join(tmpdir, "large.dat")
with open(large_file, "wb") as f:
f.write(os.urandom(1024 * 1024))
rc, _, _ = fw.run_asm(["-n", "1", large_file])
fw.report_result(rc == 0, "shred: 1MB file shred succeeds")
empty_file = os.path.join(tmpdir, "empty.dat")
with open(empty_file, "wb") as f:
pass
rc, _, _ = fw.run_asm(["-n", "1", "-z", empty_file])
fw.report_result(rc == 0, "shred: empty file shred succeeds")
boundary_file = os.path.join(tmpdir, "boundary.dat")
with open(boundary_file, "wb") as f:
f.write(os.urandom(131072))
rc, _, _ = fw.run_asm(["-n", "1", "-x", boundary_file])
fw.report_result(rc == 0, "shred: 128KB boundary file succeeds")
one_file = os.path.join(tmpdir, "one.dat")
with open(one_file, "wb") as f:
f.write(b"X")
rc, _, _ = fw.run_asm(["-n", "1", "-x", "-z", one_file])
fw.report_result(rc == 0, "shred: 1-byte file succeeds")
with open(one_file, "rb") as f:
content = f.read()
fw.report_result(content == b"\x00", "shred: 1-byte file zeroed correctly")
integrity_file = os.path.join(tmpdir, "integrity.dat")
original = b"ORIGINAL_SECRET_DATA_" * 100
with open(integrity_file, "wb") as f:
f.write(original)
rc, _, _ = fw.run_asm(["-n", "1", "-x", integrity_file])
fw.report_result(rc == 0, "shred: overwrite succeeds")
with open(integrity_file, "rb") as f:
content = f.read()
fw.report_result(len(content) == len(original), "shred: file size preserved with -x")
fw.report_result(content != original, "shred: content actually changed")
zero_file = os.path.join(tmpdir, "zeros.dat")
with open(zero_file, "wb") as f:
f.write(b"secret" * 100)
rc, _, _ = fw.run_asm(["-z", "-x", zero_file])
fw.report_result(rc == 0, "shred: zero pass succeeds")
with open(zero_file, "rb") as f:
content = f.read()
fw.report_result(all(b == 0 for b in content), "shred: zero pass produces all zeros")
remove_file = os.path.join(tmpdir, "removeme.dat")
with open(remove_file, "wb") as f:
f.write(b"remove this")
rc, _, _ = fw.run_asm(["-u", "-n", "1", remove_file])
fw.report_result(rc == 0, "shred: remove succeeds")
fw.report_result(not os.path.exists(remove_file), "shred: file actually removed")
size_file = os.path.join(tmpdir, "sized.dat")
with open(size_file, "wb") as f:
f.write(b"small")
rc, _, _ = fw.run_asm(["-s", "2048", "-z", size_file])
fw.report_result(rc == 0, "shred: -s override succeeds")
stat = os.stat(size_file)
fw.report_result(stat.st_size == 2048, f"shred: -s 2048 produced {stat.st_size} bytes")
rand_file = os.path.join(tmpdir, "random.dat")
with open(rand_file, "wb") as f:
f.write(b"\x00" * 4096)
rc, _, _ = fw.run_asm(["-n", "1", "-x", rand_file])
fw.report_result(rc == 0, "shred: random overwrite succeeds")
with open(rand_file, "rb") as f:
content = f.read()
unique_bytes = len(set(content))
fw.report_result(unique_bytes > 100, f"shred: random data has {unique_bytes} unique byte values")
long_name = os.path.join(tmpdir, "A" * 255)
try:
with open(long_name, "w") as f:
f.write("test")
rc, _, _ = fw.run_asm(["-n", "1", long_name])
fw.report_result(rc == 0, "shred: 255-char filename accepted")
except OSError:
fw.skip_test("shred: 255-char filename", "OS rejected filename")
special_file = os.path.join(tmpdir, "file with spaces.txt")
with open(special_file, "w") as f:
f.write("test")
rc, _, _ = fw.run_asm(["-n", "1", special_file])
fw.report_result(rc == 0, "shred: filename with spaces")
files = []
for i in range(50):
fpath = os.path.join(tmpdir, f"multi_{i}.txt")
with open(fpath, "w") as f:
f.write(f"data {i}")
files.append(fpath)
rc, _, _ = fw.run_asm(["-n", "1"] + files)
fw.report_result(rc == 0, "shred: 50 files at once")
rc, _, err = fw.run_asm(["-n", "abc", os.path.join(tmpdir, "basic.dat")])
fw.report_result(rc != 0, "shred: -n abc rejected")
rc, _, err = fw.run_asm(["/nonexistent/path/file"])
fw.report_result(rc != 0, "shred: nonexistent file rejected")
rc, _, err = fw.run_asm([])
fw.report_result(rc != 0, "shred: no-args exits non-zero")
fw.report_result(b"missing file operand" in err, "shred: no-args error message")
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
if os.path.exists(fw.gnu_path):
tmpdir2 = tempfile.mkdtemp()
try:
for args_desc, args in [
("1 pass", ["-v", "-n", "1"]),
("3 passes", ["-v", "-n", "3"]),
("1 pass + zero", ["-v", "-n", "1", "-z"]),
("0 passes + zero", ["-v", "-n", "0", "-z"]),
]:
gnu_file = os.path.join(tmpdir2, f"gnu_{args_desc.replace(' ', '_')}.txt")
asm_file = os.path.join(tmpdir2, f"asm_{args_desc.replace(' ', '_')}.txt")
for f in [gnu_file, asm_file]:
with open(f, "w") as fh:
fh.write("test data")
gnu_rc, _, gnu_err = fw.run_gnu(args + [gnu_file])
asm_rc, _, asm_err = fw.run_asm(args + [asm_file])
gnu_out = gnu_err.decode(errors="replace").replace(gnu_file, "FILE")
asm_out = asm_err.decode(errors="replace").replace(asm_file, "FILE")
fw.report_result(gnu_rc == asm_rc, f"compat: exit code match ({args_desc})")
pass_re = re.compile(r"pass\s+(\d+)/(\d+)\s+\(([^)]+)\)")
gnu_passes = pass_re.findall(gnu_out)
asm_passes = pass_re.findall(asm_out)
verbose_ok = (len(gnu_passes) == len(asm_passes) and
all(g[2] == a[2] for g, a in zip(gnu_passes, asm_passes)))
fw.report_result(verbose_ok, f"compat: verbose output match ({args_desc})")
gnu_rc, _, gnu_err = fw.run_gnu(["/nonexistent_xyz_file"])
asm_rc, _, asm_err = fw.run_asm(["/nonexistent_xyz_file"])
fw.report_result(gnu_rc == asm_rc, "compat: nonexistent file exit code")
gnu_rc, _, gnu_err = fw.run_gnu([])
asm_rc, _, asm_err = fw.run_asm([])
fw.report_result(gnu_rc == asm_rc, "compat: no-args exit code")
finally:
shutil.rmtree(tmpdir2, ignore_errors=True)
if __name__ == '__main__':
fw = SecurityTestFramework(config)
fw.run_all(tool_specific_fn=tool_specific_tests)