Get & Set Atomics
This page catalogs the reflective operations Python supports for reading and writing object state. These are the building blocks that the pollution primitives page combines into attacker capabilities.
Atomic “get” operations
Python has two kinds of atomic “get”. The first is attribute access (e.g., getattr(obj, name)), which retrieves values from an object’s attribute namespace and is supported by every Python object. The second is item access (e.g., dict[key]), which retrieves values from a container’s item namespace and is only supported by mappings and sequences.
The two namespaces are not interchangeable: an attribute cannot be retrieved with item lookup and vice versa. A third group, hybrid operations like eval/exec, can perform either depending on the evaluated expression.
We surveyed the Python Standard Library and identified 20 syntactic variants. Prevalence numbers come from CodeQL on the 50K most-downloaded PyPI packages.
| Code | Type | Apply | Origin | Capability | # Inst. | # Pack. |
|---|---|---|---|---|---|---|
getattr(obj, name) |
Attr | O/M/S | Builtins | 719.1K | 21.7K | |
obj.__dict__[name] |
Attr | O/M/S | Builtins | 15.3K | 3.3K | |
obj.__getattribute__(name) |
Attr | O/M/S | Builtins | 13.1K | 1.7K | |
vars(obj)[name] |
Attr | O/M/S | Builtins | 9.3K | 1.0K | |
dict(inspect.getmembers(obj))[name] |
Attr | O/M/S | Inspect | 4.9K | 1.9K | |
object.__getattribute__(obj, name) |
Attr | O/M/S | Builtins | 4.3K | 843 | |
operator.attrgetter(name)(obj) |
Attr | O/M/S | Operator | 543 | 194 | |
inspect.getattr_static(obj, name) |
Attr | O/M/S | Inspect | 398 | 121 | |
getattr(obj, dir(obj)[index]) |
Attr | O/M/S | Builtins | 93 | 23 | |
dict(inspect.getmembers_static(obj))[name] |
Attr | O/M/S | Inspect | 11 | 8 | |
dict[key] |
Item | M/S | Builtins | 50.7M | 44.1K | |
dict.get(key) |
Item | M | Builtins | 8.6M | 33.1K | |
dict.pop(key) |
Item | M/S | Builtins | 1.7M | 17.9K | |
dict.setdefault(key) |
Item | M | Builtins | 111.1K | 8.0K | |
dict.__getitem__(key) |
Item | M/S | Builtins | 63.3K | 3.0K | |
operator.getitem(dict, key) |
Item | M/S | Operator | 271 | 79 | |
operator.itemgetter(key)(dict) |
Item | M/S | Operator | 153 | 60 | |
operator.__getitem__(dict, key) |
Item | M/S | Operator | 3 | 1 | |
eval(f"EXPR", {"o": obj}) |
Attr/Item | O/M/S | Builtins | 331 | 135 | |
exec(f"EXPR", {"o": obj}) |
Attr/Item | O/M/S | Builtins | 172 | 112 |
Legend. Apply: O = Object, M = Mapping, S = Sequence. Capability shows what kinds of names the operation can retrieve, as a triple dunder / method / other: fully supported, conditionally supported (obj.__dict__[name] and vars(obj)[name] only apply when the object has a writable __dict__), not supported. EXPR in the eval/exec rows means any of the other operations above with only the key name attacker-controlled.
Atomic “set” operations
Python has two kinds of atomic “set”: attribute assignment (e.g., setattr(obj, name, val)) and item assignment (e.g., dict[key] = val). Unlike “get”, the two atomic “sets” are interchangeable when the target object has a writable __dict__: obj.x = v is semantically equivalent to obj.__dict__["x"] = v.
We surveyed the Python Standard Library and identified 12 syntactic variants. Prevalence numbers come from CodeQL on the 50K most-downloaded PyPI packages.
| Code | Type | Apply | Origin | # Inst. | # Pack. |
|---|---|---|---|---|---|
obj.__dict__[name] = val |
Attr | O/M/S | Builtins | 437.8K | 3.1K |
setattr(obj, name, val) |
Attr | O/M/S | Builtins | 214.3K | 12.0K |
object.__setattr__(obj, name, val) |
Attr | O/M/S | Builtins | 11.4K | 1.6K |
obj.__setattr__(name, val) |
Attr | O/M/S | Builtins | 10.4K | 2.3K |
dict[key] = val |
Item | M/S | Builtins | 7.7M | 36.8K |
dict.update(key=val) |
Item | M | Builtins | 687.8K | 22.7K |
dict.setdefault(key, val) |
Item | M | Builtins | 111.1K | 8.0K |
dict.__setitem__(key, val) |
Item | M/S | Builtins | 7.7K | 2.2K |
operator.setitem(dict, key, val) |
Item | M/S | Operator | 27 | 12 |
operator.__setitem__(dict, key, val) |
Item | M/S | Operator | 0 | 0 |
exec(f"EXPR", {"o": obj}) |
Attr/Item | O/M/S | Builtins | 90 | 47 |
eval(f"EXPR", {"o": obj}) |
Attr/Item | O/M/S | Builtins | 16 | 7 |
Legend. Apply: O = Object, M = Mapping, S = Sequence. The exec/eval rows count occurrences where only the key name and value are attacker-controlled.
The next page, Pollution Primitives, groups these atomics by what an attacker can choose at each step in a vulnerable program. That is what determines the variant.