Skip to content

Commit 2c53947

Browse files
committed
use libsodium-sys for mlock/munlock
1 parent bb36312 commit 2c53947

File tree

4 files changed

+215
-20
lines changed

4 files changed

+215
-20
lines changed

Cargo.lock

+63
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ crate-type = ["cdylib"]
1111
[dependencies]
1212
pyo3 = "0.21.2"
1313
zeroize_rs = { version = "1.8.1", package = "zeroize"}
14-
numpy = "0.21"
14+
numpy = "0.21"
15+
libsodium-sys = "0.2.7"
16+
libc = "0.2.155"

src/lib.rs

+133-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
1+
use std::mem;
2+
use std::sync::Once;
3+
4+
use libc::{self, size_t};
5+
use libsodium_sys::{
6+
sodium_init
7+
, sodium_mlock
8+
, sodium_munlock,
9+
};
110
use numpy::{PyArray1, PyArrayMethods};
2-
use pyo3::buffer::PyBuffer;
311
use pyo3::prelude::*;
4-
use pyo3::types::{PyByteArray, PyMemoryView};
12+
use pyo3::types::{PyByteArray, PyCFunction};
513
use zeroize_rs::Zeroize;
614

15+
/// The global [`sync::Once`] that ensures we only perform
16+
/// library initialization one time.
17+
static INIT: Once = Once::new();
18+
19+
/// A flag that returns whether this library has been safely
20+
/// initialized.
21+
static mut INITIALIZED: bool = false;
22+
723
/// A Python module implemented in Rust.
824
#[pymodule]
9-
fn zeroize(_py: Python, m: &PyModule) -> PyResult<()> {
25+
fn zeroize<'py>(py: Python, m: &Bound<'py, PyModule>) -> PyResult<()> {
1026
m.add_function(wrap_pyfunction!(zeroize1, m)?)?;
1127
m.add_function(wrap_pyfunction!(zeroize_np, m)?)?;
1228
// m.add_function(wrap_pyfunction!(zeroize_mv, m)?)?;
29+
m.add_function(wrap_pyfunction!(mlock, m)?)?;
30+
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)?)?;
1333
Ok(())
1434
}
1535

@@ -26,6 +46,58 @@ fn zeroize_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
2646
Ok(())
2747
}
2848

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+
}
58+
}
59+
Ok(())
60+
}
61+
62+
#[pyfunction]
63+
fn mlock_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
64+
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")
70+
}
71+
}
72+
Ok(())
73+
}
74+
75+
#[pyfunction]
76+
fn munlock<'py>(arr: &Bound<'py, PyByteArray>) -> PyResult<()> {
77+
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")
83+
}
84+
}
85+
Ok(())
86+
}
87+
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")
96+
}
97+
}
98+
Ok(())
99+
}
100+
29101
// #[pyfunction]
30102
// fn zeroize_mv<'py>(arr: &PyMemoryView, len: usize) -> PyResult<()> {
31103
// // Get the buffer information
@@ -41,3 +113,61 @@ fn zeroize_np<'py>(arr: &Bound<'py, PyArray1<u8>>) -> PyResult<()> {
41113
//
42114
// Ok(())
43115
// }
116+
117+
/// Initialized libsodium. This function *must* be called at least once
118+
/// prior to using any of the other functions in this library, and
119+
/// callers *must* verify that it returns `true`. If it returns `false`,
120+
/// libsodium was unable to be properly set up and this library *must
121+
/// not* be used.
122+
///
123+
/// Calling it multiple times is a no-op.
124+
pub(crate) fn init() -> bool {
125+
unsafe {
126+
INIT.call_once(|| {
127+
// NOTE: Calls to transmute fail to compile if the source
128+
// and destination type have a different size. We (ab)use
129+
// this fact to statically assert the size of types at
130+
// compile-time.
131+
//
132+
// We assume that we can freely cast between rust array
133+
// sizes and [`libc::size_t`]. If that's not true, DO NOT
134+
// COMPILE.
135+
#[allow(clippy::useless_transmute)]
136+
let _ = std::mem::transmute::<usize, size_t>(0);
137+
138+
let mut failure = false;
139+
140+
// sodium_init returns 0 on success, -1 on failure, and 1 if
141+
// the library is already initialized; someone else might
142+
// have already initialized it before us, so we only care
143+
// about failure
144+
failure |= sodium_init() == -1;
145+
146+
INITIALIZED = !failure;
147+
});
148+
149+
INITIALIZED
150+
}
151+
}
152+
153+
/// Calls the platform's underlying `mlock(2)` implementation.
154+
unsafe fn _mlock<T>(ptr: *mut T) -> bool {
155+
sodium_mlock(ptr.cast(), mem::size_of::<T>()) == 0
156+
}
157+
158+
/// Calls the platform's underlying `munlock(2)` implementation.
159+
unsafe fn _munlock<T>(ptr: *mut T) -> bool {
160+
sodium_munlock(ptr.cast(), mem::size_of::<T>()) == 0
161+
}
162+
163+
#[cfg(test)]
164+
mod test {
165+
use zeroize_rs::Zeroize;
166+
167+
#[test]
168+
fn test_zeroize() {
169+
let mut arr = [1, 2, 3, 4, 5];
170+
arr.zeroize();
171+
assert_eq!(arr, [0, 0, 0, 0, 0]);
172+
}
173+
}

tests/test_zeroize.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
import zeroize
2+
from zeroize import zeroize1, zeroize_np, mlock, munlock, mlock_np, munlock_np
33
import numpy as np
44
import ctypes
55
import platform
@@ -121,52 +121,52 @@ class TestStringMethods(unittest.TestCase):
121121
def test_zeroize1(self):
122122
try:
123123
arr = bytearray(b"1234567890")
124-
lock_memory(arr)
125-
zeroize.zeroize1(arr)
124+
mlock(arr)
125+
zeroize1(arr)
126126
self.assertEqual(
127127
arr, bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
128128
)
129129

130130
finally:
131-
unlock_memory(arr)
131+
munlock(arr)
132132

133133
def test_zeroize_np(self):
134134
try:
135135
arr = np.array([0] * 10, dtype=np.uint8)
136-
lock_memory(arr)
136+
mlock_np(arr)
137137
arr[:] = bytes(b"1234567890")
138-
zeroize.zeroize_np(arr)
138+
zeroize_np(arr)
139139
self.assertEqual(True, all(arr == 0))
140140

141141
finally:
142-
unlock_memory(arr)
142+
munlock_np(arr)
143143

144144
def test_zeroize1_sizes(self):
145145
for size in SIZES_MB:
146146
try:
147147
arr = bytearray(int(size * 1024 * 1024))
148-
lock_memory(arr)
149-
zeroize.zeroize1(arr)
148+
mlock(arr)
149+
zeroize1(arr)
150150
self.assertEqual(arr, bytearray(int(size * 1024 * 1024)))
151151

152152
finally:
153-
unlock_memory(arr)
153+
munlock(arr)
154154

155155
def test_zeroize_np_sizes(self):
156156
for size in SIZES_MB:
157157
try:
158158
array_size = int(size * 1024 * 1024)
159159
random_array = np.random.randint(0, 256, array_size, dtype=np.uint8)
160-
lock_memory(random_array)
161-
zeroize.zeroize_np(random_array)
160+
mlock_np(random_array)
161+
zeroize_np(random_array)
162162
self.assertEqual(True, all(random_array == 0))
163163
finally:
164-
unlock_memory(random_array)
164+
munlock_np(random_array)
165165

166166

167167
if __name__ == "__main__":
168-
if os_name == "Windows":
169-
# Enable the privilege to lock memory
170-
enable_lock_memory_privilege()
168+
# if os_name == "Windows":
169+
# # Enable the privilege to lock memory
170+
# enable_lock_memory_privilege()
171171

172172
unittest.main()

0 commit comments

Comments
 (0)