Control: tags -1 + patchIt looks like the Python 3.13 FTBFS in aflplusplus is caused by a failure to properly handle exceptions when initialising a Python mutator. I don't have a good understanding of Python's C interface, but it looks like when PyObject_GetAttrString() is called on a key that doesn't exist, it returns NULL and raises an AttributeError. And that somehow gets stored in the Python interpreter and emerges when AFL++ first tries to call Python code, which it does in init_py(). This is how you end up with an AttributeError apparently caused by calling random.seed().
According to the documentation at <https://docs.python.org/3/c-api/object.html#c.PyObject_GetAttrString>, if you don't want an exception in these circumstances, you can use PyObject_GetOptionalAttrString() instead. That doesn't raise AttributeError, though it can raise other exceptions. It's available starting in Python 3.13.
The attached patch switches init_py_module() to use PyObject_GetOptionalAttr() in place of PyObject_GetAttrString(). This is sufficient to get the package to compile, passing its tests, with Python 3.13. The patch also adds a version to the python3-dev build-dependency to support this.
However, this is not really a good fix. Any other exception raised by PyObject_GetOptionalAttr() will still be ignored, and will presumably emerge in init_py(). A better approach would be to add proper handling of Python exceptions to init_py_module(). And a patch that requires Python 3.13 probably won't be acceptable upstream right now.
Still, it's not any worse than the code that's already there, and it means that at least well-behaved Python mutators will work again.
-- Ben Harris
diff --git a/debian/control b/debian/control index 7bdddea..69bc136 100644 --- a/debian/control +++ b/debian/control @@ -14,7 +14,7 @@ Build-Depends: gcc-multilib [amd64 i386 mips64el mipsel ppc64 s390x sparc64 x32], lld-19 [!s390x], llvm-19-dev, - python3-dev, + python3-dev (>= 3.13~), # for the tests clang, libcmocka-dev, diff --git a/debian/patches/replace-all-pyobject_getattrstring-with-.patch b/debian/patches/replace-all-pyobject_getattrstring-with-.patch new file mode 100644 index 0000000..b31f085 --- /dev/null +++ b/debian/patches/replace-all-pyobject_getattrstring-with-.patch @@ -0,0 +1,105 @@ +From: Ben Harris <bj...@bjh21.me.uk> +Date: Thu, 9 Jan 2025 21:54:14 +0000 +X-Dgit-Generated: 4.21c-5~1.gbp3dcdb9 f3ad11aa08a06c8862b7c49b45ced0c78350d1e0 +Subject: Replace all PyObject_GetAttrString() with ...Optional...() + +This suppresses the AttributeError that would otherwise be raised when +the attribute doesn't exist. It doesn't suppress other exceptions, +though, and we still don't handle those properly. + +This enables compiling with Python 3.13. Without this patch, the last +AttributeError from PyObject_GetAttrString() seems to get saved and +raised when we next call into Python code, which happens in init_py(). +Unfortunately, using PyObject_GetOptionalAttrString() means that this +patch doesn't work with any Python older than 3.13. + +Bug-Debian: 1091402 + +--- + +diff --git a/src/afl-fuzz-python.c b/src/afl-fuzz-python.c +index 873b25e..2a91c98 100644 +--- a/src/afl-fuzz-python.c ++++ b/src/afl-fuzz-python.c +@@ -220,45 +220,50 @@ static py_mutator_t *init_py_module(afl_state_t *afl, u8 *module_name) { + if (py_module != NULL) { + + u8 py_notrim = 0; +- py_functions[PY_FUNC_INIT] = PyObject_GetAttrString(py_module, "init"); ++ PyObject_GetOptionalAttrString(py_module, "init", ++ &py_functions[PY_FUNC_INIT]); + if (!py_functions[PY_FUNC_INIT]) { + + WARNF("init function not found in python module"); + + } + +- py_functions[PY_FUNC_FUZZ] = PyObject_GetAttrString(py_module, "fuzz"); ++ PyObject_GetOptionalAttrString(py_module, "fuzz", ++ &py_functions[PY_FUNC_FUZZ]); + if (!py_functions[PY_FUNC_FUZZ]) +- py_functions[PY_FUNC_FUZZ] = PyObject_GetAttrString(py_module, "mutate"); +- py_functions[PY_FUNC_DESCRIBE] = +- PyObject_GetAttrString(py_module, "describe"); +- py_functions[PY_FUNC_FUZZ_COUNT] = +- PyObject_GetAttrString(py_module, "fuzz_count"); +- py_functions[PY_FUNC_POST_PROCESS] = +- PyObject_GetAttrString(py_module, "post_process"); +- py_functions[PY_FUNC_INIT_TRIM] = +- PyObject_GetAttrString(py_module, "init_trim"); +- py_functions[PY_FUNC_POST_TRIM] = +- PyObject_GetAttrString(py_module, "post_trim"); +- py_functions[PY_FUNC_TRIM] = PyObject_GetAttrString(py_module, "trim"); +- py_functions[PY_FUNC_HAVOC_MUTATION] = +- PyObject_GetAttrString(py_module, "havoc_mutation"); +- py_functions[PY_FUNC_HAVOC_MUTATION_PROBABILITY] = +- PyObject_GetAttrString(py_module, "havoc_mutation_probability"); +- py_functions[PY_FUNC_QUEUE_GET] = +- PyObject_GetAttrString(py_module, "queue_get"); +- py_functions[PY_FUNC_FUZZ_SEND] = +- PyObject_GetAttrString(py_module, "fuzz_send"); +- py_functions[PY_FUNC_POST_RUN] = +- PyObject_GetAttrString(py_module, "post_run"); +- py_functions[PY_FUNC_SPLICE_OPTOUT] = +- PyObject_GetAttrString(py_module, "splice_optout"); ++ PyObject_GetOptionalAttrString(py_module, "mutate", ++ &py_functions[PY_FUNC_FUZZ]); ++ PyObject_GetOptionalAttrString(py_module, "describe", ++ &py_functions[PY_FUNC_DESCRIBE]); ++ PyObject_GetOptionalAttrString(py_module, "fuzz_count", ++ &py_functions[PY_FUNC_FUZZ_COUNT]); ++ PyObject_GetOptionalAttrString(py_module, "post_process", ++ &py_functions[PY_FUNC_POST_PROCESS]); ++ PyObject_GetOptionalAttrString(py_module, "init_trim", ++ &py_functions[PY_FUNC_INIT_TRIM]); ++ PyObject_GetOptionalAttrString(py_module, "post_trim", ++ &py_functions[PY_FUNC_POST_TRIM]); ++ PyObject_GetOptionalAttrString(py_module, "trim", ++ &py_functions[PY_FUNC_TRIM]); ++ PyObject_GetOptionalAttrString(py_module, "havoc_mutation", ++ &py_functions[PY_FUNC_HAVOC_MUTATION]); ++ PyObject_GetOptionalAttrString(py_module, "havoc_mutation_probability", ++ &py_functions[PY_FUNC_HAVOC_MUTATION_PROBABILITY]); ++ PyObject_GetOptionalAttrString(py_module, "queue_get", ++ &py_functions[PY_FUNC_QUEUE_GET]); ++ PyObject_GetOptionalAttrString(py_module, "fuzz_send", ++ &py_functions[PY_FUNC_FUZZ_SEND]); ++ PyObject_GetOptionalAttrString(py_module, "post_run", ++ &py_functions[PY_FUNC_POST_RUN]); ++ PyObject_GetOptionalAttrString(py_module, "splice_optout", ++ &py_functions[PY_FUNC_SPLICE_OPTOUT]); + if (py_functions[PY_FUNC_SPLICE_OPTOUT]) { afl->custom_splice_optout = 1; } +- py_functions[PY_FUNC_QUEUE_NEW_ENTRY] = +- PyObject_GetAttrString(py_module, "queue_new_entry"); +- py_functions[PY_FUNC_INTROSPECTION] = +- PyObject_GetAttrString(py_module, "introspection"); +- py_functions[PY_FUNC_DEINIT] = PyObject_GetAttrString(py_module, "deinit"); ++ PyObject_GetOptionalAttrString(py_module, "queue_new_entry", ++ &py_functions[PY_FUNC_QUEUE_NEW_ENTRY]); ++ PyObject_GetOptionalAttrString(py_module, "introspection", ++ &py_functions[PY_FUNC_INTROSPECTION]); ++ PyObject_GetOptionalAttrString(py_module, "deinit", ++ &py_functions[PY_FUNC_DEINIT] ); + if (!py_functions[PY_FUNC_DEINIT]) + WARNF("deinit function not found in python module"); + diff --git a/debian/patches/series b/debian/patches/series index 25de15c..0c74927 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,4 @@ Do-not-ignore-failing-gcc_plugin-install.patch Disable-builds-trying-to-access-network.patch Ignore-laf-intel-compcov-test-failure-for-s390x-arch.patch +replace-all-pyobject_getattrstring-with-.patch