conflict_set.py constructs _Key objects from ephemeral bytearray/ctypes arrays without keeping a reference to the underlying buffer. Once the local variables in write()/read() go out of scope, the _Key.p pointer may point to freed memory by the time ConflictSet_check/ConflictSet_addWrites is called.
Location:
conflict_set.py:31 and conflict_set.py:41: b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
conflict_set.py:36 and conflict_set.py:45: e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
conflict_set.py:37 and conflict_set.py:46: return ... _Key(b, len(b)), _Key(e, len(e)) ...
Expected behavior: A WriteRange or ReadRange returned by write()/read() keeps its key bytes alive for the lifetime of the object, so the C library always reads valid memory. Actual behavior: The WriteRange/ReadRange only stores a raw pointer and length; the ctypes arrays and backing bytearray objects are discarded when the helper returns. This is a classic ctypes use-after-free pattern.
Impact: Any caller that passes the returned range object to addWrites()/check() after the original bytes/buffer is no longer directly referenced can read freed memory, leading to incorrect conflict-set results or a crash. For example:
fromconflict_setimport*cs=ConflictSet(0)# The temporary bytearray is gone after this expressioncs.addWrites(1,write(b"key"))
Even when the bug does not crash immediately, it is a latent memory-safety issue in the public Python API.
Suggested fix: Store the backing ctypes arrays (or the original bytes/bytearray) inside the WriteRange/ReadRange objects, e.g. by adding private _begin_buf / _end_buf attributes, so Python keeps them alive as long as the range object exists.
`conflict_set.py` constructs `_Key` objects from ephemeral `bytearray`/`ctypes` arrays without keeping a reference to the underlying buffer. Once the local variables in `write()`/`read()` go out of scope, the `_Key.p` pointer may point to freed memory by the time `ConflictSet_check`/`ConflictSet_addWrites` is called.
**Location:**
- `conflict_set.py:31` and `conflict_set.py:41`: `b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))`
- `conflict_set.py:36` and `conflict_set.py:45`: `e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))`
- `conflict_set.py:37` and `conflict_set.py:46`: `return ... _Key(b, len(b)), _Key(e, len(e)) ...`
**Expected behavior:** A `WriteRange` or `ReadRange` returned by `write()`/`read()` keeps its key bytes alive for the lifetime of the object, so the C library always reads valid memory.
**Actual behavior:** The `WriteRange`/`ReadRange` only stores a raw pointer and length; the `ctypes` arrays and backing `bytearray` objects are discarded when the helper returns. This is a classic ctypes use-after-free pattern.
**Impact:** Any caller that passes the returned range object to `addWrites()`/`check()` after the original `bytes`/buffer is no longer directly referenced can read freed memory, leading to incorrect conflict-set results or a crash. For example:
```python
from conflict_set import *
cs = ConflictSet(0)
# The temporary bytearray is gone after this expression
cs.addWrites(1, write(b"key"))
```
Even when the bug does not crash immediately, it is a latent memory-safety issue in the public Python API.
**Suggested fix:** Store the backing `ctypes` arrays (or the original `bytes`/`bytearray`) inside the `WriteRange`/`ReadRange` objects, e.g. by adding private `_begin_buf` / `_end_buf` attributes, so Python keeps them alive as long as the range object exists.
weaselbot
was assigned by andrew2026-06-19 01:15:23 +00:00
weaselbot
was assigned by andrew2026-06-19 01:19:33 +00:00
I have implemented the fix for this issue on branch weaselbot/issue-42 and pushed it to my fork, but I cannot open a pull request because the upstream repository weaselab/conflict-set has pull requests disabled (has_pull_requests: false in the repo API response, and POST /api/v1/repos/weaselab/conflict-set/pulls returns not found).
Could a maintainer either enable pull requests on the upstream repo or let me know the preferred way to submit this change? The code change and tests are ready for review.
I have implemented the fix for this issue on branch `weaselbot/issue-42` and pushed it to my fork, but I cannot open a pull request because the upstream repository `weaselab/conflict-set` has pull requests disabled (`has_pull_requests: false` in the repo API response, and `POST /api/v1/repos/weaselab/conflict-set/pulls` returns `not found`).
Could a maintainer either enable pull requests on the upstream repo or let me know the preferred way to submit this change? The code change and tests are ready for review.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
conflict_set.pyconstructs_Keyobjects from ephemeralbytearray/ctypesarrays without keeping a reference to the underlying buffer. Once the local variables inwrite()/read()go out of scope, the_Key.ppointer may point to freed memory by the timeConflictSet_check/ConflictSet_addWritesis called.Location:
conflict_set.py:31andconflict_set.py:41:b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))conflict_set.py:36andconflict_set.py:45:e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))conflict_set.py:37andconflict_set.py:46:return ... _Key(b, len(b)), _Key(e, len(e)) ...Expected behavior: A
WriteRangeorReadRangereturned bywrite()/read()keeps its key bytes alive for the lifetime of the object, so the C library always reads valid memory.Actual behavior: The
WriteRange/ReadRangeonly stores a raw pointer and length; thectypesarrays and backingbytearrayobjects are discarded when the helper returns. This is a classic ctypes use-after-free pattern.Impact: Any caller that passes the returned range object to
addWrites()/check()after the originalbytes/buffer is no longer directly referenced can read freed memory, leading to incorrect conflict-set results or a crash. For example:Even when the bug does not crash immediately, it is a latent memory-safety issue in the public Python API.
Suggested fix: Store the backing
ctypesarrays (or the originalbytes/bytearray) inside theWriteRange/ReadRangeobjects, e.g. by adding private_begin_buf/_end_bufattributes, so Python keeps them alive as long as the range object exists.I have implemented the fix for this issue on branch
weaselbot/issue-42and pushed it to my fork, but I cannot open a pull request because the upstream repositoryweaselab/conflict-sethas pull requests disabled (has_pull_requests: falsein the repo API response, andPOST /api/v1/repos/weaselab/conflict-set/pullsreturnsnot found).Could a maintainer either enable pull requests on the upstream repo or let me know the preferred way to submit this change? The code change and tests are ready for review.
Pull requests should be enabled now