test_verify_win7_bundle.py
Rama main · réplica de lectura del repositorio original.
"""Contrôles PE Win7 sans construire ni charger de DLL native."""
from __future__ import annotations
import tempfile
import unittest
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
try:
import verify_win7_bundle as verification
except ModuleNotFoundError as erreur:
if erreur.name != "pefile":
raise
raise unittest.SkipTest("pefile absent : contrôle réservé à l'environnement de build")
def _imports(dll, *symboles):
return SimpleNamespace(
dll=dll,
imports=[SimpleNamespace(name=nom) for nom in symboles],
)
def _pe(imports=(), differes=(), machine=verification.AMD64):
return SimpleNamespace(
FILE_HEADER=SimpleNamespace(Machine=machine),
DIRECTORY_ENTRY_IMPORT=imports,
DIRECTORY_ENTRY_DELAY_IMPORT=differes,
parse_data_directories=mock.Mock(),
close=mock.Mock(),
)
class LecturePETests(unittest.TestCase):
def test_interface_historique_et_imports_differes(self):
pe = _pe(
[_imports(b"KERNEL32.dll", b"GetProcAddress")],
[_imports(b"BCRYPTPRIMITIVES.DLL", b"ProcessPrng")],
)
with mock.patch.object(verification.pefile, "PE", return_value=pe):
resultat = verification._lire_pe(Path("native.pyd"))
self.assertEqual(resultat, (
verification.AMD64, {"kernel32.dll", "bcryptprimitives.dll"}
))
pe.close.assert_called_once_with()
pe.parse_data_directories.assert_called_once_with(directories=[
verification.pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"],
verification.pefile.DIRECTORY_ENTRY[
"IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT"
],
])
def test_symboles_uniques_et_ordinals_ignores(self):
entree = _imports(b"KERNEL32.dll", b"CreateFile2", None)
pe = _pe([entree], [entree])
with mock.patch.object(verification.pefile, "PE", return_value=pe):
machine, dlls, symboles = verification._lire_pe_details(Path("x"))
self.assertEqual(machine, verification.AMD64)
self.assertEqual(dlls, {"kernel32.dll"})
self.assertEqual(symboles, {("kernel32.dll", "CreateFile2")})
def test_fichier_non_pe_conserve_le_resultat_historique(self):
with mock.patch.object(
verification.pefile, "PE",
side_effect=verification.pefile.PEFormatError("pas un PE"),
):
self.assertEqual(verification._lire_pe(Path("x")), (None, set()))
def test_ferme_le_pe_meme_si_la_lecture_echoue(self):
pe = _pe()
pe.parse_data_directories.side_effect = RuntimeError("lecture")
with mock.patch.object(verification.pefile, "PE", return_value=pe):
with self.assertRaisesRegex(RuntimeError, "lecture"):
verification._lire_pe_details(Path("x"))
pe.close.assert_called_once_with()
class VerificationBundleTests(unittest.TestCase):
def setUp(self):
temporaire = tempfile.TemporaryDirectory()
self.addCleanup(temporaire.cleanup)
self.bundle = Path(temporaire.name)
interne = self.bundle / "_internal"
(interne / "certifi").mkdir(parents=True)
(self.bundle / "blink2video.exe").touch()
(interne / "python38.dll").touch()
(interne / "windows7-build.txt").touch()
(interne / "certifi" / "cacert.pem").write_bytes(b"x" * 100_000)
self.native = interne / "native.pyd"
self.native.touch()
def _verifier(self, pe):
def lire(chemin, **kwargs):
return pe if Path(chemin) == self.native else _pe()
with mock.patch.object(verification.pefile, "PE", side_effect=lire):
with redirect_stdout(StringIO()) as sortie:
erreurs = verification.verifier(self.bundle)
return erreurs, sortie.getvalue()
def test_chaque_api_interdite_normale_ou_differee(self):
for symbole in verification.API_POST_WIN7:
for differe in (False, True):
with self.subTest(symbole=symbole, differe=differe):
dll = (b"BCRYPTPRIMITIVES.DLL" if symbole == "ProcessPrng"
else b"KERNEL32.dll")
entree = _imports(dll, symbole.encode("ascii"))
pe = _pe(differes=[entree]) if differe else _pe([entree])
erreurs, sortie = self._verifier(pe)
self.assertEqual(len(erreurs), 1, erreurs)
self.assertIn(str(self.native.relative_to(self.bundle)),
erreurs[0])
self.assertIn(dll.decode().lower() + "!" + symbole,
erreurs[0])
self.assertNotIn("OK :", sortie)
def test_api_sets_et_kernelbase_sont_controles(self):
for dll in (b"API-MS-Win-Core-Synch-l1-2-0.dll", b"KernelBase.dll",
b"EXT-MS-Win-Kernel32-Synch-l1-1-0.dll"):
with self.subTest(dll=dll):
erreurs, _ = self._verifier(_pe([
_imports(dll, b"WaitOnAddress")
]))
self.assertEqual(len(erreurs), 1, erreurs)
self.assertIn(dll.decode().lower() + "!WaitOnAddress",
erreurs[0])
def test_getprocaddress_et_chaines_dynamiques_ne_sont_pas_des_imports(self):
# Ces noms peuvent exister comme chaînes pour un GetProcAddress
# optionnel : seules les tables PE ont autorité pour ce garde-fou.
self.native.write_bytes(b"ProcessPrng\x00WaitOnAddress\x00CreateFile2")
erreurs, sortie = self._verifier(_pe([
_imports(b"KERNEL32.dll", b"GetProcAddress", b"LoadLibraryW",
b"GetSystemTimeAsFileTime")
]))
self.assertEqual(erreurs, [])
self.assertIn("OK :", sortie)
def test_homonyme_dans_une_dll_applicative_autorise(self):
erreurs, _ = self._verifier(_pe([
_imports(b"application.dll", b"WaitOnAddress", b"ProcessPrng")
]))
self.assertEqual(erreurs, [])
def test_dll_interdite_reste_interdite_meme_par_ordinal(self):
erreurs, _ = self._verifier(_pe(differes=[
_imports(verification.DLL_INTERDITE.upper().encode(), None)
]))
self.assertEqual(len(erreurs), 1, erreurs)
self.assertIn(verification.DLL_INTERDITE, erreurs[0])
self.assertIn(str(self.native.relative_to(self.bundle)), erreurs[0])
def test_architecture_reste_controlee(self):
erreurs, _ = self._verifier(_pe(machine=0x14C))
self.assertEqual(len(erreurs), 1, erreurs)
self.assertIn("non x86-64", erreurs[0])
if __name__ == "__main__":
unittest.main()