Skip to content

Commit 0418cc7

Browse files
committed
Add _operator._compare_digest
1 parent 86c098b commit 0418cc7

File tree

5 files changed

+92
-3
lines changed

5 files changed

+92
-3
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/hmac.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
"""
55

66
import warnings as _warnings
7-
# XXX RustPython TODO: _operator
8-
#from _operator import _compare_digest as compare_digest
7+
from _operator import _compare_digest as compare_digest
98
import hashlib as _hashlib
109

1110
trans_5C = bytes((x ^ 0x5C) for x in range(256))

tests/snippets/stdlib_operator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import _operator
2+
3+
assert _operator._compare_digest("abcdef", "abcdef")
4+
assert not _operator._compare_digest("abcdef", "abc")
5+
assert not _operator._compare_digest("abc", "abcdef")

vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sha-1 = "0.8"
2222
sha2 = "0.8"
2323
sha3 = "0.8"
2424
blake2 = "0.8"
25+
volatile = "0.2"
2526

2627
num-complex = { version = "0.2.2", features = ["serde"] }
2728
num-bigint = { version = "0.2.4", features = ["serde"] }

vm/src/stdlib/operator.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::function::OptionalArg;
2+
use crate::obj::objbyteinner::PyBytesLike;
3+
use crate::obj::objstr::PyStringRef;
24
use crate::obj::{objiter, objtype};
3-
use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol};
5+
use crate::pyobject::{Either, PyObjectRef, PyResult, TypeProtocol};
46
use crate::VirtualMachine;
7+
use volatile::Volatile;
58

69
fn operator_length_hint(obj: PyObjectRef, default: OptionalArg, vm: &VirtualMachine) -> PyResult {
710
let default = default.unwrap_or_else(|| vm.new_int(0));
@@ -17,8 +20,82 @@ fn operator_length_hint(obj: PyObjectRef, default: OptionalArg, vm: &VirtualMach
1720
Ok(hint)
1821
}
1922

23+
#[inline(never)]
24+
#[cold]
25+
fn timing_safe_cmp(a: &[u8], b: &[u8]) -> bool {
26+
// we use raw pointers here to keep faithful to the C implementation and
27+
// to try to avoid any optimizations rustc might do with slices
28+
let len_a = a.len();
29+
let a = a.as_ptr();
30+
let len_b = b.len();
31+
let b = b.as_ptr();
32+
/* The volatile type declarations make sure that the compiler has no
33+
* chance to optimize and fold the code in any way that may change
34+
* the timing.
35+
*/
36+
let length: Volatile<usize>;
37+
let mut left: Volatile<*const u8>;
38+
let mut right: Volatile<*const u8>;
39+
let mut result: u8 = 0;
40+
41+
/* loop count depends on length of b */
42+
length = Volatile::new(len_b);
43+
left = Volatile::new(std::ptr::null());
44+
right = Volatile::new(b);
45+
46+
/* don't use else here to keep the amount of CPU instructions constant,
47+
* volatile forces re-evaluation
48+
* */
49+
if len_a == length.read() {
50+
left.write(Volatile::new(a).read());
51+
result = 0;
52+
}
53+
if len_a != length.read() {
54+
left.write(b);
55+
result = 1;
56+
}
57+
58+
for _ in 0..length.read() {
59+
let l = left.read();
60+
left.write(l.wrapping_add(1));
61+
let r = right.read();
62+
right.write(r.wrapping_add(1));
63+
// safety: the 0..length range will always be either:
64+
// * as long as the length of both a and b, if len_a and len_b are equal
65+
// * as long as b, and both `left` and `right` are b
66+
result |= unsafe { l.read_volatile() ^ r.read_volatile() };
67+
}
68+
69+
result == 0
70+
}
71+
72+
fn operator_compare_digest(
73+
a: Either<PyStringRef, PyBytesLike>,
74+
b: Either<PyStringRef, PyBytesLike>,
75+
vm: &VirtualMachine,
76+
) -> PyResult<bool> {
77+
let res = match (a, b) {
78+
(Either::A(a), Either::A(b)) => {
79+
if !a.as_str().is_ascii() || !b.as_str().is_ascii() {
80+
return Err(vm.new_type_error(
81+
"comparing strings with non-ASCII characters is not supported".to_string(),
82+
));
83+
}
84+
timing_safe_cmp(a.as_str().as_bytes(), b.as_str().as_bytes())
85+
}
86+
(Either::B(a), Either::B(b)) => a.with_ref(|a| b.with_ref(|b| timing_safe_cmp(a, b))),
87+
_ => {
88+
return Err(vm.new_type_error(
89+
"unsupported operand types(s) or combination of types".to_string(),
90+
))
91+
}
92+
};
93+
Ok(res)
94+
}
95+
2096
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
2197
py_module!(vm, "_operator", {
2298
"length_hint" => vm.ctx.new_function(operator_length_hint),
99+
"_compare_digest" => vm.ctx.new_function(operator_compare_digest),
23100
})
24101
}

0 commit comments

Comments
 (0)