Skip to content

Commit 9ec482f

Browse files
committed
Integrate cereggii fuzzing, make some adjustments.
1 parent cd87a07 commit 9ec482f

12 files changed

+446
-31
lines changed

fusil/python/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ def createFuzzerOptions(self, parser: OptionParserWithSections) -> None:
187187
action="store_true",
188188
default=False,
189189
)
190+
fuzzing_options.add_option(
191+
'--fuzz-cereggii-scenarios',
192+
help='Run only specialized cereggii fuzzing scenarios instead of general API fuzzing.',
193+
action='store_true',
194+
default=False,
195+
)
190196
fuzzing_options.add_option(
191197
"--filenames",
192198
help="Names separated by commas of readable files (default: %s)" % FILENAMES,

fusil/python/argument_generator.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@
6767
except ImportError:
6868
TEMPLATES = []
6969

70+
try:
71+
from fusil.python import tricky_cereggii
72+
_HAS_TRICKY_CEREGGII = True
73+
print("Loaded tricky_cereggii aggregator for ArgumentGenerator.")
74+
except ImportError:
75+
_HAS_TRICKY_CEREGGII = False
76+
print("Warning: Could not load tricky_cereggii aggregator for ArgumentGenerator.")
77+
tricky_cereggii = None # Define for type checking
78+
7079
try:
7180
import numpy
7281
except ImportError:
@@ -107,6 +116,10 @@ def __init__(
107116
self.filenames = filenames
108117
self.errback_name = ERRBACK_NAME_CONST
109118

119+
is_cereggii_target = (
120+
self.options.modules == "cereggii" or getattr(self.options, "fuzz_cereggii_scenarios", False)
121+
)
122+
110123
self.h5py_argument_generator = H5PyArgumentGenerator(self) if use_h5py and H5PyArgumentGenerator else None
111124

112125
# Initialize generators for various data types
@@ -158,6 +171,35 @@ def __init__(
158171
self.genTrickyObjects,
159172
)
160173

174+
if is_cereggii_target and _HAS_TRICKY_CEREGGII:
175+
print("Activating cereggii-specific argument generators...")
176+
# Add hashable cereggii objects
177+
self.hashable_argument_generators += (
178+
self.genTrickyAtomicInt64, # AtomicInt64 is hashable
179+
self.genTrickyHashableKeyCereggii, # Keys specifically chosen for hashability
180+
) * 10 # Weight: make them appear reasonably often
181+
182+
# Add simple (potentially non-hashable) cereggii objects
183+
self.simple_argument_generators += (
184+
self.genTrickyWeirdCereggii,
185+
self.genTrickyRecursiveCereggii,
186+
self.genTrickyThreadHandle,
187+
) * 10
188+
189+
# Add complex cereggii objects (AtomicDict itself)
190+
self.complex_argument_generators += (
191+
self.genTrickyAtomicDict,
192+
) * 10
193+
194+
# Also add the simple ones to complex, as complex can include simple
195+
self.complex_argument_generators += (
196+
self.genTrickyAtomicInt64,
197+
self.genTrickyHashableKeyCereggii,
198+
self.genTrickyWeirdCereggii,
199+
self.genTrickyRecursiveCereggii,
200+
self.genTrickyThreadHandle,
201+
) * 5 # Lower weight here as they are already in simple
202+
161203
# Handle NumPy, h5py, and t-strings conditionally
162204
if not self.options.no_numpy and use_numpy and H5PyArgumentGenerator:
163205
if allow_external_references:
@@ -289,6 +331,60 @@ def genInterestingValues(self) -> list[str]:
289331
"""Generate an 'interesting' predefined value."""
290332
return [choice(INTERESTING)]
291333

334+
def genTrickyAtomicInt64(self) -> list[str]:
335+
"""Generate a reference to a tricky AtomicInt64 instance."""
336+
# Check if the aggregator and its specific list are available
337+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_atomicint64_instance_names:
338+
# Fallback to creating a simple default instance if tricky ones aren't loaded
339+
return ["cereggii.AtomicInt64(0)"]
340+
# Select a random name from the aggregated list
341+
name = choice(tricky_cereggii.tricky_atomicint64_instance_names)
342+
# Return the code to access it from the dictionary defined in the boilerplate
343+
# The name 'tricky_atomic_ints' must match the variable in tricky_atomicint64.py
344+
return [f"tricky_atomic_ints['{name}']"]
345+
346+
def genTrickyAtomicDict(self) -> list[str]:
347+
"""Generate a reference to a tricky AtomicDict instance."""
348+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_atomicdict_instance_names:
349+
return ["cereggii.AtomicDict()"] # Fallback
350+
name = choice(tricky_cereggii.tricky_atomicdict_instance_names)
351+
# Assumes 'tricky_atomic_dicts' dict is defined in boilerplate
352+
return [f"tricky_atomic_dicts['{name}']"]
353+
354+
def genTrickyWeirdCereggii(self) -> list[str]:
355+
"""Generate a reference to a weird cereggii subclass instance."""
356+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_weird_cereggii_instance_names:
357+
return ["object()"] # Generic fallback
358+
name = choice(tricky_cereggii.tricky_weird_cereggii_instance_names)
359+
# Assumes 'tricky_weird_cereggii_objects' dict is defined in boilerplate
360+
return [f"tricky_weird_cereggii_objects['{name}']"]
361+
362+
def genTrickyRecursiveCereggii(self) -> list[str]:
363+
"""Generate a reference to a tricky recursive cereggii object."""
364+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_recursive_object_names:
365+
return ["['recursive_fallback']"] # Fallback
366+
name = choice(tricky_cereggii.tricky_recursive_object_names)
367+
# Assumes 'tricky_recursive_objects' dict is defined in boilerplate
368+
return [f"tricky_recursive_objects['{name}']"]
369+
370+
def genTrickyThreadHandle(self) -> list[str]:
371+
"""Generate a reference to a tricky ThreadHandle instance or callable."""
372+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_threadhandle_instance_names:
373+
# Fallback needs a valid object to wrap
374+
return ["cereggii.ThreadHandle(None)"]
375+
name = choice(tricky_cereggii.tricky_threadhandle_instance_names)
376+
# Assumes 'tricky_threadhandle_collection' dict is defined in boilerplate
377+
return [f"tricky_threadhandle_collection['{name}']"]
378+
379+
def genTrickyHashableKeyCereggii(self) -> list[str]:
380+
"""Generate a reference to a tricky but hashable object for use as a dict key."""
381+
# Use the specific list aggregated in tricky_atomicdict.py
382+
if not _HAS_TRICKY_CEREGGII or not tricky_cereggii or not tricky_cereggii.tricky_hashable_key_names:
383+
return ["'fallback_key'"] # Fallback to a simple string
384+
name = choice(tricky_cereggii.tricky_hashable_key_names)
385+
# Assumes 'tricky_hashable_keys' dict is defined in boilerplate
386+
return [f"tricky_hashable_keys['{name}']"]
387+
292388
def genTrickyObjects(self) -> list[str]:
293389
"""Generate a name of a 'tricky' predefined object from tricky_weird."""
294390
tricky_name = choice(fusil.python.tricky_weird.tricky_objects_names)

fusil/python/blacklists.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@
232232
"Lock",
233233
"RLock",
234234
"Semaphore",
235-
"AtomicInt64"
236235
}
237236
METHOD_BLACKLIST = {
238237
"__class__",
@@ -248,7 +247,6 @@
248247
"_randbelow",
249248
"_randbelow_with_getrandbits",
250249
"_read",
251-
"_rehash",
252250
"_run_once",
253251
"_serve",
254252
"_shutdown",

fusil/python/python_source.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ def __init__(self, project: Project, options: FusilConfig, source_output_path: s
4848
verbose=self.options.verbose,
4949
).search_modules()
5050

51+
if self.options.fuzz_cereggii_scenarios:
52+
self.error("Cereggii Scenario Mode: Forcing target module to 'cereggii'.")
53+
54+
self.modules = {'cereggii'}
55+
5156
if self.options.packages != "*":
5257
print("\nAdding packages...")
5358
all_modules = ListAllModules(
@@ -59,7 +64,12 @@ def __init__(self, project: Project, options: FusilConfig, source_output_path: s
5964
verbose=self.options.verbose,
6065
)
6166

62-
for package in self.options.packages.split(","):
67+
packages = self.options.packages.split(",")
68+
if self.options.fuzz_cereggii_scenarios:
69+
self.error("Cereggii Scenario Mode: Forcing packages to 'cereggii'.")
70+
packages = ["cereggii"]
71+
72+
for package in packages:
6373
package = package.strip().strip("/")
6474
if not len(package):
6575
continue
@@ -119,6 +129,7 @@ def loadModule(self, module_name: str) -> None:
119129
self.module_name,
120130
threads=not self.options.no_threads,
121131
_async=not self.options.no_async,
132+
is_cereggii_scenario_mode=getattr(self.options, 'fuzz_cereggii_scenarios', False),
122133
)
123134

124135
def on_session_start(self) -> None:

fusil/python/samples/cereggii/tricky_atomicdict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# We gather a wide range of pre-existing tricky objects to use as keys/values.
1717
# This is wrapped in try-except to allow this module to run standalone.
1818
try:
19-
from fusil.python.samples import tricky_objects, weird_classes # , tricky_numpy
19+
from fusil.python.samples import tricky_objects, weird_classes as weird_classes_module # , tricky_numpy
2020

2121
_HAS_DEPS = True
2222
except ImportError:
@@ -205,7 +205,7 @@ def __repr__(self):
205205
if _HAS_DEPS:
206206
all_tricky_sources = {
207207
"tricky_obj": tricky_objects.__dict__,
208-
"weird_cls": weird_classes.weird_instances,
208+
"weird_cls": weird_classes_module.weird_instances,
209209
# "tricky_np": tricky_numpy.__dict__,
210210
}
211211
for source_name, source_dict in all_tricky_sources.items():

fusil/python/samples/cereggii/tricky_atomicint64.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# We wrap this in a try-except block to allow this module to be run standalone
1313
# without breaking, which can be useful for debugging the generated inputs.
1414
try:
15-
from fusil.python.samples import weird_classes
15+
from fusil.python.samples import weird_classes as weird_classes_module
1616

1717
_HAS_WEIRD_CLASSES = True
1818
except ImportError:
@@ -188,14 +188,14 @@ def side_effect_callable(current_value):
188188
if _HAS_WEIRD_CLASSES:
189189
# Add callables that return instances from our other tricky modules.
190190
weird_callables["callable_ret_weird_list"] = (
191-
lambda x: weird_classes.weird_instances["weird_list_empty"]
191+
lambda x: weird_classes_module.weird_instances["weird_list_empty"]
192192
)
193193

194194
# This callable returns a FrameModifier. When the C code DECREFs this returned
195195
# object, its __del__ method will be triggered, attempting to maliciously
196196
# modify variables in its caller's frame. This is a potent attack against
197197
# assumptions made by JIT compilers or C code about object lifetimes.
198-
weird_callables["callable_frame_modifier"] = lambda x: weird_classes.FrameModifier(
198+
weird_callables["callable_frame_modifier"] = lambda x: weird_classes_module.FrameModifier(
199199
"side_effect_target", 999
200200
)
201201

fusil/python/samples/cereggii/tricky_atomicint_scenarios.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# We make imports of our other tricky modules optional to allow for modularity.
1919
try:
2020
from fusil.python.samples.cereggii import tricky_atomicint64
21-
from fusil.python.samples import weird_classes
21+
from fusil.python.samples import weird_classes as weird_classes_module
2222
except ImportError:
2323
print(
2424
"Warning: Could not import tricky_atomicint64 or weird_classes. "
@@ -35,7 +35,7 @@ class WeirdClassesMock:
3535
weird_instances = {}
3636

3737
tricky_atomicint64 = TrickyAtomicInt64Mock()
38-
weird_classes = WeirdClassesMock()
38+
weird_classes_module = WeirdClassesMock()
3939

4040

4141
# --- Step 1.2: Aggregate a "Menu" of Operations and Operands ---
@@ -48,8 +48,8 @@ class WeirdClassesMock:
4848
operator.truediv,
4949
operator.floordiv,
5050
operator.mod,
51-
operator.pow,
52-
operator.lshift,
51+
# operator.pow,
52+
# operator.lshift,
5353
operator.rshift,
5454
operator.and_,
5555
operator.or_,
@@ -58,14 +58,15 @@ class WeirdClassesMock:
5858

5959
# A parallel list of all in-place operators.
6060
_INPLACE_OPS = [
61+
6162
operator.iadd,
6263
operator.isub,
6364
operator.imul,
6465
operator.itruediv,
6566
operator.ifloordiv,
6667
operator.imod,
67-
operator.ipow,
68-
operator.ilshift,
68+
# operator.ipow,
69+
# operator.ilshift,
6970
operator.irshift,
7071
operator.iand,
7172
operator.ior,
@@ -80,7 +81,7 @@ class WeirdClassesMock:
8081
_ALL_NUMERIC_OPERANDS.extend(tricky_atomicint64.overflow_operands)
8182
# 3. Add all number-like "weird" instances.
8283
_ALL_NUMERIC_OPERANDS.extend(
83-
inst for name, inst in weird_classes.weird_instances.items() if "weird_int" in name
84+
inst for name, inst in weird_classes_module.weird_instances.items() if "weird_int" in name
8485
)
8586
# 4. Add fundamental tricky numerics.
8687
_ALL_NUMERIC_OPERANDS.extend(
@@ -117,23 +118,30 @@ def worker():
117118
target_atomic_int = random.choice(
118119
tricky_atomicint64.atomic_int_instances_for_binops
119120
)
120-
121+
print(f"{target_atomic_int.get()=}")
121122
# 2. Pick a random, potentially malicious, right-hand operand.
122123
right_hand_operand = random.choice(_ALL_NUMERIC_OPERANDS)
124+
if hasattr(right_hand_operand, "get"):
125+
print(f"{right_hand_operand.get()=}")
126+
else:
127+
print(f"{right_hand_operand=}")
123128

124129
# 3. Randomly choose the type of operation to perform.
125130
op_type = random.choice(["binary", "inplace", "reflected"])
126-
131+
print(f"{op_type=}: ", end="")
127132
try:
128133
if op_type == "inplace":
129134
op = random.choice(_INPLACE_OPS)
135+
print(f"{op=}")
130136
op(target_atomic_int, right_hand_operand)
131137
elif op_type == "binary":
132138
op = random.choice(_BINARY_OPS)
139+
print(f"{op=}")
133140
op(target_atomic_int, right_hand_operand)
134141
elif op_type == "reflected":
135142
# For reflected ops, the non-atomic operand comes first.
136143
op = random.choice(_BINARY_OPS)
144+
print(f"{op=}")
137145
op(right_hand_operand, target_atomic_int)
138146

139147
except (OverflowError, TypeError, ValueError, ZeroDivisionError) as e:

fusil/python/samples/cereggii/tricky_python_utils_scenarios.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
or methods to attack the underlying C implementations indirectly. Also includes
55
scenarios testing interactions between different utilities.
66
"""
7+
import math
78

89
import cereggii
910
import sys
@@ -16,12 +17,12 @@
1617

1718
# --- Imports for Tricky Objects ---
1819
try:
19-
from fusil.python.samples import weird_classes
20+
from fusil.python.samples import weird_classes as weird_classes_module
2021

2122
print("Successfully imported weird_classes for Python utils scenarios.")
2223
except ImportError:
2324
print("Warning: Could not import weird_classes.", file=sys.stderr)
24-
weird_classes = None
25+
weird_classes_module = None
2526

2627
# REMOVED: try...except block for tricky_weird_cereggii
2728

@@ -30,8 +31,8 @@
3031

3132
# Collect integer-like weird objects for the poison constructor attack
3233
_TRICKY_INTS_FOR_LATCH = []
33-
if weird_classes:
34-
for name, instance in weird_classes.weird_instances.items():
34+
if weird_classes_module:
35+
for name, instance in weird_classes_module.weird_instances.items():
3536
# Check if it inherits from int/number and isn't just a basic type instance
3637
if "weird_" in name and isinstance(
3738
instance, (int, float, complex)
@@ -74,7 +75,7 @@ def scenario_poison_countdownlatch(num_waiters=4, num_decrementers=4):
7475
# Attempt to create the latch with the malicious object
7576
latch = cereggii.CountDownLatch(poison_object)
7677
print(
77-
f"Successfully created CountDownLatch with {type(poison_object).__name__}"
78+
f"Successfully created CountDownLatch with {type(poison_object).__name__}, log10={math.log10(poison_object)}"
7879
)
7980
except AssertionError:
8081
continue # Expected failure for negative values etc.
@@ -216,7 +217,7 @@ def wait_worker():
216217

217218
# --- Aggregate and Export Scenarios ---
218219
python_utils_scenarios = {
219-
"scenario_poison_countdownlatch": scenario_poison_countdownlatch,
220+
# "scenario_poison_countdownlatch": scenario_poison_countdownlatch,
220221
"scenario_latch_decremented_by_reduce": scenario_latch_decremented_by_reduce, # Added new scenario
221222
}
222223

fusil/python/samples/cereggii/tricky_reduce_nightmares.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
print("Warning: 'tricky_atomicdict.py' not found.", file=sys.stderr)
2525

2626
try:
27-
from fusil.python.samples.weird_classes import weird_instances
27+
from fusil.python.samples.weird_classes import weird_instances as _weird_instances
2828
except ImportError:
29-
weird_instances = None
29+
_weird_instances = None
3030
print("Warning: 'weird_classes.py' not found.", file=sys.stderr)
3131

3232

@@ -171,7 +171,7 @@ def __bool__(self):
171171

172172

173173
_non_hashable_key = (
174-
weird_instances.get("weird_list_empty", []) if weird_instances else []
174+
_weird_instances.get("weird_list_empty", []) if _weird_instances else []
175175
)
176176

177177
specialized_breakers = {

0 commit comments

Comments
 (0)