Skip to content

Commit ed569c2

Browse files
committed
have Rust methods accept PyAny and convert inside
1 parent 9df7104 commit ed569c2

File tree

6 files changed

+105
-173
lines changed

6 files changed

+105
-173
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "zeroize"
3-
version = "0.1.11"
3+
version = "0.2.0"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

README.md

Lines changed: 54 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -12,133 +12,89 @@ It can work with `bytearray` and `numpy array`.
1212

1313
> [!WARNING]
1414
> **In the case of [Copy-on-write fork](https://en.wikipedia.org/wiki/Copy-on-write) you need to zeroize the memory before forking the child process, see example below.
15-
> Also by itself it doesn't work if memory is moved or moved to swap. You can use `zeroize.mlock()` and `zeroize.mlock_np()` to lock the memory, max size you can lock is 4MB, at least on Linux, see example below.**
15+
> Also by itself it doesn't work if memory is moved or moved to swap. You can use `zeroize.mlock()` and `zeroize.mlock()` to lock the memory, max size you can lock is 4MB, at least on Linux, see example below.**
1616
1717
# Examples
1818

1919
## Lock and zeroize memory
2020

2121
```python
22-
from zeroize import zeroize1, zeroize_np
23-
import numpy as np
24-
import ctypes
25-
26-
27-
# Load the C standard library
28-
LIBC = ctypes.CDLL("libc.so.6")
29-
MLOCK = LIBC.mlock
30-
MUNLOCK = LIBC.munlock
31-
32-
# Define mlock and munlock argument types
33-
MLOCK.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
34-
MUNLOCK.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
35-
36-
37-
def lock_memory(buffer):
38-
"""Locks the memory of the given buffer."""
39-
address = ctypes.addressof(ctypes.c_char.from_buffer(buffer))
40-
size = len(buffer)
41-
if MLOCK(address, size) != 0:
42-
raise RuntimeError("Failed to lock memory")
22+
"""By itself it doesn't work if memory is moved or moved to swap. You can use `crypes` with `libc.mlock()` to lock the memory"""
4323

24+
from zeroize import zeroize1, mlock, munlock
25+
import numpy as np
4426

45-
def unlock_memory(buffer):
46-
"""Unlocks the memory of the given buffer."""
47-
address = ctypes.addressof(ctypes.c_char.from_buffer(buffer))
48-
size = len(buffer)
49-
if MUNLOCK(address, size) != 0:
50-
raise RuntimeError("Failed to unlock memory")
5127

28+
if __name__ == "__main__":
29+
try:
30+
print("allocate memory")
5231

53-
try:
54-
print("allocate memory")
32+
# regular array
33+
# max size you can lock is 4MB, at least on Linux
34+
arr = bytearray(b"1234567890")
5535

56-
# regular array
57-
arr = bytearray(b"1234567890")
36+
# numpy array
37+
# max size you can lock is 4MB, at least on Linux
38+
arr_np = np.array([0] * 10, dtype=np.uint8)
39+
arr_np[:] = arr
40+
assert arr_np.tobytes() == b"1234567890"
5841

59-
# numpy array
60-
arr_np = np.array([0] * 10, dtype=np.uint8)
61-
arr_np[:] = arr
62-
assert arr_np.tobytes() == b"1234567890"
42+
print("locking memory")
6343

64-
print("locking memory")
44+
mlock(arr)
45+
mlock(arr_np)
6546

66-
lock_memory(arr)
67-
lock_memory(arr_np)
47+
print("zeroize'ing...: ")
48+
zeroize1(arr)
49+
zeroize1(arr_np)
6850

69-
print("zeroize'ing...: ")
70-
zeroize1(arr)
71-
zeroize_np(arr_np)
51+
print("checking if is zeroized")
52+
assert arr == bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
53+
assert all(arr_np == 0)
7254

73-
print("checking if is zeroized")
74-
assert arr == bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
75-
assert all(arr_np == 0)
55+
print("all good, bye!")
7656

77-
print("all good, bye!")
78-
finally:
79-
# Unlock the memory
80-
print("unlocking memory")
81-
unlock_memory(arr)
82-
unlock_memory(arr_np)
57+
finally:
58+
# Unlock the memory
59+
print("unlocking memory")
60+
munlock(arr)
61+
munlock(arr_np)
8362
```
8463

85-
## Zeroing memory before starting child process
64+
## Zeroing memory before forking child process
8665

8766
This mitigates the problems that appears on [Copy-on-write fork](https://en.wikipedia.org/wiki/Copy-on-write). You need to zeroize the data before forking the child process.
8867
```python
89-
import os
90-
from zeroize import zeroize1, zeroize_np
91-
import numpy as np
92-
import ctypes
93-
94-
95-
# Load the C standard library
96-
LIBC = ctypes.CDLL("libc.so.6")
97-
MLOCK = LIBC.mlock
98-
MUNLOCK = LIBC.munlock
99-
100-
# Define mlock and munlock argument types
101-
MLOCK.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
102-
MUNLOCK.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
103-
104-
105-
def lock_memory(buffer):
106-
"""Locks the memory of the given buffer."""
107-
address = ctypes.addressof(ctypes.c_char.from_buffer(buffer))
108-
size = len(buffer)
109-
if MLOCK(address, size) != 0:
110-
raise RuntimeError("Failed to lock memory")
68+
""" In the case of [Copy-on-write fork](https://en.wikipedia.org/wiki/Copy-on-write) you need to zeroize the memory before forking the child process. """
11169

70+
import os
71+
from zeroize import zeroize1, mlock, munlock
11272

113-
def unlock_memory(buffer):
114-
"""Unlocks the memory of the given buffer."""
115-
address = ctypes.addressof(ctypes.c_char.from_buffer(buffer))
116-
size = len(buffer)
117-
if MUNLOCK(address, size) != 0:
118-
raise RuntimeError("Failed to unlock memory")
11973

74+
if __name__ == "__main__":
75+
try:
76+
# max size you can lock is 4MB, at least on Linux
77+
sensitive_data = bytearray(b"Sensitive Information")
78+
mlock(sensitive_data)
12079

121-
try:
122-
sensitive_data = bytearray(b"Sensitive Information")
123-
lock_memory(sensitive_data)
80+
print("Before zeroization:", sensitive_data)
12481

125-
print("Before zeroization:", sensitive_data)
82+
zeroize1(sensitive_data)
83+
print("After zeroization:", sensitive_data)
12684

127-
zeroize1(sensitive_data)
128-
print("After zeroization:", sensitive_data)
85+
# Forking after zeroization to ensure no sensitive data is copied
86+
pid = os.fork()
87+
if pid == 0:
88+
# This is the child process
89+
print("Child process memory after fork:", sensitive_data)
90+
else:
91+
# This is the parent process
92+
os.wait() # Wait for the child process to exit
12993

130-
# Forking after zeroization to ensure no sensitive data is copied
131-
pid = os.fork()
132-
if pid == 0:
133-
# This is the child process
134-
print("Child process memory after fork:", sensitive_data)
135-
else:
136-
# This is the parent process
137-
os.wait() # Wait for the child process to exit
138-
finally:
139-
# Unlock the memory
140-
print("unlocking memory")
141-
unlock_memory(sensitive_data)
94+
finally:
95+
# Unlock the memory
96+
print("unlocking memory")
97+
munlock(sensitive_data)
14298
```
14399

144100
# Building from source

examples/lock_and_zeroize.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""By itself it doesn't work if memory is moved or moved to swap. You can use `crypes` with `libc.mlock()` to lock the memory"""
22

3-
from zeroize import zeroize1, zeroize_np, mlock, munlock, mlock_np, munlock_np
3+
from zeroize import zeroize1, mlock, munlock
44
import numpy as np
55

66

@@ -21,11 +21,11 @@
2121
print("locking memory")
2222

2323
mlock(arr)
24-
mlock_np(arr_np)
24+
mlock(arr_np)
2525

2626
print("zeroize'ing...: ")
2727
zeroize1(arr)
28-
zeroize_np(arr_np)
28+
zeroize1(arr_np)
2929

3030
print("checking if is zeroized")
3131
assert arr == bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
@@ -37,4 +37,4 @@
3737
# Unlock the memory
3838
print("unlocking memory")
3939
munlock(arr)
40-
munlock_np(arr_np)
40+
munlock(arr_np)

src/lib.rs

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,78 +24,66 @@ static mut INITIALIZED: bool = false;
2424
#[pymodule]
2525
fn zeroize<'py>(_py: Python, m: &Bound<'py, PyModule>) -> PyResult<()> {
2626
m.add_function(wrap_pyfunction!(zeroize1, m)?)?;
27-
m.add_function(wrap_pyfunction!(zeroize_np, m)?)?;
2827
// m.add_function(wrap_pyfunction!(zeroize_mv, m)?)?;
2928
m.add_function(wrap_pyfunction!(mlock, m)?)?;
3029
m.add_function(wrap_pyfunction!(munlock, m)?)?;
31-
m.add_function(wrap_pyfunction!(mlock_np, m)?)?;
32-
m.add_function(wrap_pyfunction!(munlock_np, m)?)?;
3330
Ok(())
3431
}
3532

3633

3734
#[pyfunction]
38-
fn zeroize1<'py>(arr: &Bound<'py, PyByteArray>) -> PyResult<()> {
39-
unsafe { arr.as_bytes_mut().zeroize(); }
35+
fn zeroize1<'py>(arr: &Bound<'py, PyAny>) -> PyResult<()> {
36+
as_array(arr)?.zeroize();
4037
Ok(())
4138
}
4239

4340
#[pyfunction]
44-
fn zeroize_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
45-
unsafe { arr.as_slice_mut().unwrap().zeroize(); }
46-
Ok(())
47-
}
48-
49-
#[pyfunction]
50-
fn mlock<'py>(arr: &Bound<'py, PyByteArray>) -> PyResult<()> {
51-
unsafe {
52-
if !init() {
53-
panic!("libsodium failed to initialize")
54-
}
55-
if !_mlock(arr.as_bytes_mut().as_mut_ptr()) {
56-
panic!("mlock failed")
57-
}
41+
fn mlock<'py>(arr: &Bound<'py, PyAny>) -> PyResult<()> {
42+
if !init() {
43+
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
44+
"libsodium failed to initialize",
45+
));
5846
}
59-
Ok(())
60-
}
61-
62-
#[pyfunction]
63-
fn mlock_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
6447
unsafe {
65-
if !init() {
66-
panic!("libsodium failed to initialize")
67-
}
68-
if !_mlock(arr.as_slice_mut().unwrap().as_mut_ptr()) {
69-
panic!("mlock failed")
48+
if !_mlock(as_array(arr)?.as_mut_ptr()) {
49+
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
50+
"mlock failed",
51+
));
7052
}
7153
}
7254
Ok(())
7355
}
7456

7557
#[pyfunction]
76-
fn munlock<'py>(arr: &Bound<'py, PyByteArray>) -> PyResult<()> {
58+
fn munlock<'py>(arr: &Bound<'py, PyAny>) -> PyResult<()> {
59+
if !init() {
60+
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
61+
"libsodium failed to initialize",
62+
));
63+
}
7764
unsafe {
78-
if !init() {
79-
panic!("libsodium failed to initialize")
80-
}
81-
if !_munlock(arr.as_bytes_mut().as_mut_ptr()) {
82-
panic!("mlock failed")
65+
if !_munlock(as_array(arr)?.as_mut_ptr()) {
66+
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
67+
"mlock failed",
68+
));
8369
}
8470
}
8571
Ok(())
8672
}
8773

88-
#[pyfunction]
89-
fn munlock_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
90-
unsafe {
91-
if !init() {
92-
panic!("libsodium failed to initialize")
93-
}
94-
if !_munlock(arr.as_slice_mut().unwrap().as_mut_ptr()) {
95-
panic!("mlock failed")
74+
fn as_array<'a>(arr: &'a Bound<PyAny>) -> PyResult<&'a mut [u8]> {
75+
let arr = unsafe {
76+
if let Ok(arr) = arr.downcast::<PyByteArray>() {
77+
arr.as_bytes_mut()
78+
} else if let Ok(arr) = arr.downcast::<PyArray1<u8>>() {
79+
arr.as_slice_mut().unwrap()
80+
} else {
81+
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
82+
"Expected a PyByteArray or PyArray1<u8>",
83+
));
9684
}
97-
}
98-
Ok(())
85+
};
86+
Ok(arr)
9987
}
10088

10189
// #[pyfunction]
@@ -121,7 +109,7 @@ fn munlock_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
121109
/// not* be used.
122110
///
123111
/// Calling it multiple times is a no-op.
124-
pub(crate) fn init() -> bool {
112+
fn init() -> bool {
125113
unsafe {
126114
INIT.call_once(|| {
127115
// NOTE: Calls to transmute fail to compile if the source

0 commit comments

Comments
 (0)