From ee9c30569f68f4382c75ba0a3ab59ccd50238994 Mon Sep 17 00:00:00 2001 From: andoriyaprashant Date: Fri, 13 Feb 2026 06:03:40 +0530 Subject: [PATCH 1/2] Add fuzz tests for CPython Go bindings (cpython.go) --- cpython/cpython_fuzz_test.go | 236 +++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 cpython/cpython_fuzz_test.go diff --git a/cpython/cpython_fuzz_test.go b/cpython/cpython_fuzz_test.go new file mode 100644 index 00000000..32cad4f4 --- /dev/null +++ b/cpython/cpython_fuzz_test.go @@ -0,0 +1,236 @@ +// MFP - Multi-Function Printers and scanners toolkit +// CPython binding. +// +// Fuzz tests for Python interpreter lifecycle, Eval/Exec and Go->Python object conversion. +// +//go:build linux || darwin || windows + +package cpython + +import ( + "math" + "math/big" + "strings" + "testing" + "time" +) + +// Helpers + +func containsFatalPython(s string) bool { + return strings.Contains(s, "sys.exit") || + strings.Contains(s, "os._exit") || + strings.Contains(s, "raise SystemExit") +} + +func safeEval(py *Python, src string) { + done := make(chan struct{}) + go func() { + defer close(done) + _ = py.Eval(src) + }() + + select { + case <-done: + case <-time.After(100 * time.Millisecond): + } +} + +func safeExec(py *Python, src string) { + done := make(chan struct{}) + go func() { + defer close(done) + _ = py.Exec(src, "") + }() + + select { + case <-done: + case <-time.After(100 * time.Millisecond): + } +} + +// Global interpreter for fuzz worker + +var fuzzPy *Python + +func getFuzzPython(t *testing.T) *Python { + if fuzzPy != nil { + return fuzzPy + } + + py, err := NewPython() + if err != nil { + t.Skip("Python interpreter not available in fuzz worker") + return nil + } + + fuzzPy = py + return fuzzPy +} + +// Interpreter fuzzing + +func FuzzPythonEvalExec(f *testing.F) { + f.Add("1 + 1") + f.Add("print('hello')") + f.Add("") + f.Add("x = [1, 2, 3]\nx") + f.Add("def f(x): return x * 2\nf(10)") + f.Add("1/0") + f.Add("this is not python") + + f.Fuzz(func(t *testing.T, src string) { + if containsFatalPython(src) { + t.Skip() + } + + py := getFuzzPython(t) + if py == nil { + return + } + + safeEval(py, src) + safeExec(py, src) + }) +} + +// Object conversion fuzzing + +func FuzzPythonNewObjectInt64(f *testing.F) { + f.Add(int64(0)) + f.Add(int64(-1)) + f.Add(int64(1)) + f.Add(int64(1 << 60)) + + f.Fuzz(func(t *testing.T, v int64) { + py := getFuzzPython(t) + if py == nil { + return + } + + obj := py.NewObject(v) + if obj != nil { + _ = obj.Err() + } + + bi := big.NewInt(v) + obj = py.NewObject(bi) + if obj != nil { + _ = obj.Err() + } + }) +} + +func FuzzPythonNewObjectFloat64(f *testing.F) { + f.Add(float64(0)) + f.Add(float64(1.5)) + f.Add(float64(-1.5)) + f.Add(float64(math.MaxFloat64)) + + f.Fuzz(func(t *testing.T, v float64) { + py := getFuzzPython(t) + if py == nil { + return + } + + obj := py.NewObject(v) + if obj != nil { + _ = obj.Err() + } + }) +} + +func FuzzPythonNewObjectString(f *testing.F) { + f.Add("") + f.Add("hello") + f.Add("привет") + + f.Fuzz(func(t *testing.T, s string) { + py := getFuzzPython(t) + if py == nil { + return + } + + obj := py.NewObject(s) + if obj != nil { + _ = obj.Err() + } + }) +} + +func FuzzPythonNewObjectBytes(f *testing.F) { + f.Add([]byte{}) + f.Add([]byte{0x00, 0x01, 0xff}) + + f.Fuzz(func(t *testing.T, b []byte) { + py := getFuzzPython(t) + if py == nil { + return + } + + obj := py.NewObject(b) + if obj != nil { + _ = obj.Err() + } + }) +} + +func FuzzPythonContainerConversion(f *testing.F) { + f.Add(0) + f.Add(1) + f.Add(5) + + f.Fuzz(func(t *testing.T, n int) { + if n < 0 { + return + } + if n > 20 { + n = 20 + } + + py := getFuzzPython(t) + if py == nil { + return + } + + slice := make([]any, 0, n) + for i := 0; i < n; i++ { + slice = append(slice, i) + } + obj := py.NewObject(slice) + if obj != nil { + _ = obj.Err() + } + + m := make(map[string]int) + for i := 0; i < n; i++ { + m[string('a'+rune(i%26))] = i + } + obj = py.NewObject(m) + if obj != nil { + _ = obj.Err() + } + }) +} + +func FuzzPythonGlobals(f *testing.F) { + f.Add("x", int64(1)) + f.Add("", int64(0)) + + f.Fuzz(func(t *testing.T, name string, v int64) { + py := getFuzzPython(t) + if py == nil { + return + } + + _ = py.SetGlobal(name, v) + + obj := py.GetGlobal(name) + if obj != nil { + _ = obj.Err() + } + + _, _ = py.ContainsGlobal(name) + _, _ = py.DelGlobal(name) + }) +} From 5063e9f56531a1ffe1eb5b9d34c553faba8cbfcc Mon Sep 17 00:00:00 2001 From: andoriyaprashant Date: Sun, 15 Feb 2026 02:05:03 +0530 Subject: [PATCH 2/2] Improve fuzz tests: add comments, fix seeds and validate Eval/Exec errors --- cpython/cpython_fuzz_test.go | 59 ++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/cpython/cpython_fuzz_test.go b/cpython/cpython_fuzz_test.go index 32cad4f4..c4781bc7 100644 --- a/cpython/cpython_fuzz_test.go +++ b/cpython/cpython_fuzz_test.go @@ -17,35 +17,45 @@ import ( // Helpers +// containsFatalPython filters out Python code that would terminate the process func containsFatalPython(s string) bool { return strings.Contains(s, "sys.exit") || strings.Contains(s, "os._exit") || strings.Contains(s, "raise SystemExit") } -func safeEval(py *Python, src string) { - done := make(chan struct{}) +// safeEval runs py.Eval with a timeout to avoid blocking fuzz workers +func safeEval(py *Python, src string) error { + done := make(chan error, 1) go func() { - defer close(done) - _ = py.Eval(src) + obj := py.Eval(src) + if obj != nil { + done <- obj.Err() + return + } + done <- nil }() select { - case <-done: + case err := <-done: + return err case <-time.After(100 * time.Millisecond): + return nil } } -func safeExec(py *Python, src string) { - done := make(chan struct{}) +// safeExec runs py.Exec with a timeout to avoid blocking fuzz workers +func safeExec(py *Python, src string) error { + done := make(chan error, 1) go func() { - defer close(done) - _ = py.Exec(src, "") + done <- py.Exec(src, "") }() select { - case <-done: + case err := <-done: + return err case <-time.After(100 * time.Millisecond): + return nil } } @@ -68,16 +78,14 @@ func getFuzzPython(t *testing.T) *Python { return fuzzPy } -// Interpreter fuzzing - +// FuzzPythonEvalExec fuzzes Python Eval/Exec to ensure that arbitrary input does not crash the interpreter or leave it in a broken state func FuzzPythonEvalExec(f *testing.F) { f.Add("1 + 1") - f.Add("print('hello')") f.Add("") - f.Add("x = [1, 2, 3]\nx") + f.Add("x = [1, 2, 3]") f.Add("def f(x): return x * 2\nf(10)") - f.Add("1/0") - f.Add("this is not python") + f.Add("1/0") // runtime error + f.Add("this is not python") // syntax error f.Fuzz(func(t *testing.T, src string) { if containsFatalPython(src) { @@ -89,13 +97,19 @@ func FuzzPythonEvalExec(f *testing.F) { return } - safeEval(py, src) - safeExec(py, src) + errEval := safeEval(py, src) + errExec := safeExec(py, src) + + // For known invalid inputs, we expect errors + if src == "1/0" || src == "this is not python" { + if errEval == nil && errExec == nil { + t.Fatalf("expected error for input %q, got nil", src) + } + } }) } -// Object conversion fuzzing - +// FuzzPythonNewObjectInt64 fuzzes conversion of integer values from Go to Python objects func FuzzPythonNewObjectInt64(f *testing.F) { f.Add(int64(0)) f.Add(int64(-1)) @@ -121,6 +135,7 @@ func FuzzPythonNewObjectInt64(f *testing.F) { }) } +// FuzzPythonNewObjectFloat64 fuzzes conversion of float values from Go to Python objects func FuzzPythonNewObjectFloat64(f *testing.F) { f.Add(float64(0)) f.Add(float64(1.5)) @@ -140,6 +155,7 @@ func FuzzPythonNewObjectFloat64(f *testing.F) { }) } +// FuzzPythonNewObjectString fuzzes string conversion from Go to Python unicode objects func FuzzPythonNewObjectString(f *testing.F) { f.Add("") f.Add("hello") @@ -158,6 +174,7 @@ func FuzzPythonNewObjectString(f *testing.F) { }) } +// FuzzPythonNewObjectBytes fuzzes byte slice conversion to Python bytes objects func FuzzPythonNewObjectBytes(f *testing.F) { f.Add([]byte{}) f.Add([]byte{0x00, 0x01, 0xff}) @@ -175,6 +192,7 @@ func FuzzPythonNewObjectBytes(f *testing.F) { }) } +// FuzzPythonContainerConversion fuzzes conversion of Go slices and maps to Python containers func FuzzPythonContainerConversion(f *testing.F) { f.Add(0) f.Add(1) @@ -213,6 +231,7 @@ func FuzzPythonContainerConversion(f *testing.F) { }) } +// FuzzPythonGlobals fuzzes access to Python global variables via Set/Get/Contains/Del func FuzzPythonGlobals(f *testing.F) { f.Add("x", int64(1)) f.Add("", int64(0))