Skip to content

Commit 40c6781

Browse files
committed
Merge remote-tracking branch 'origin/main' into pr/702
2 parents 5cac2ed + a6202ae commit 40c6781

6 files changed

Lines changed: 234 additions & 57 deletions

File tree

cryptovec/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2024"
66
license = "Apache-2.0"
77
name = "russh-cryptovec"
88
repository = "https://github.com/warp-tech/russh"
9-
version = "0.59.0"
9+
version = "0.60.3"
1010
rust-version = "1.85"
1111

1212
[dependencies]

cryptovec/src/cryptovec.rs

Lines changed: 106 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,57 @@ impl Default for CryptoVec {
153153
}
154154
}
155155

156+
const MAX_CAPACITY: usize = 1usize << (usize::BITS - 2);
157+
158+
#[cold]
159+
#[inline(never)]
160+
#[allow(clippy::panic)]
161+
fn capacity_overflow(len: usize) -> ! {
162+
panic!("CryptoVec capacity overflow: {len}")
163+
}
164+
165+
#[cold]
166+
#[inline(never)]
167+
#[allow(clippy::panic)]
168+
fn length_overflow(lhs: usize, rhs: usize) -> ! {
169+
panic!("CryptoVec length overflow: {lhs} + {rhs}")
170+
}
171+
172+
#[cold]
173+
#[inline(never)]
174+
fn alloc_failed(layout: std::alloc::Layout) -> ! {
175+
std::alloc::handle_alloc_error(layout)
176+
}
177+
178+
#[inline]
179+
fn checked_capacity(len: usize) -> usize {
180+
if len > MAX_CAPACITY {
181+
capacity_overflow(len);
182+
}
183+
len.next_power_of_two()
184+
}
185+
186+
#[inline]
187+
unsafe fn alloc_zeroed(capacity: usize) -> *mut u8 {
188+
debug_assert!(capacity > 0);
189+
let layout = unsafe { std::alloc::Layout::from_size_align_unchecked(capacity, 1) };
190+
let p = unsafe { std::alloc::alloc_zeroed(layout) };
191+
if p.is_null() {
192+
alloc_failed(layout);
193+
}
194+
let _ = mlock(p, capacity);
195+
p
196+
}
197+
198+
#[inline]
199+
fn checked_len_sum(lhs: usize, rhs: usize) -> usize {
200+
let sum = lhs.wrapping_add(rhs);
201+
if sum < lhs {
202+
length_overflow(lhs, rhs);
203+
}
204+
sum
205+
}
206+
156207
impl CryptoVec {
157208
/// Creates a new `CryptoVec`.
158209
pub fn new() -> CryptoVec {
@@ -161,27 +212,27 @@ impl CryptoVec {
161212

162213
/// Creates a new `CryptoVec` with `n` zeros.
163214
pub fn new_zeroed(size: usize) -> CryptoVec {
164-
unsafe {
165-
let capacity = size.next_power_of_two();
166-
let layout = std::alloc::Layout::from_size_align_unchecked(capacity, 1);
167-
let p = std::alloc::alloc_zeroed(layout);
168-
let _ = mlock(p, capacity);
169-
CryptoVec { p, capacity, size }
215+
if size == 0 {
216+
return CryptoVec::default();
170217
}
218+
219+
let capacity = checked_capacity(size);
220+
let p = unsafe { alloc_zeroed(capacity) };
221+
CryptoVec { p, capacity, size }
171222
}
172223

173224
/// Creates a new `CryptoVec` with capacity `capacity`.
174225
pub fn with_capacity(capacity: usize) -> CryptoVec {
175-
unsafe {
176-
let capacity = capacity.next_power_of_two();
177-
let layout = std::alloc::Layout::from_size_align_unchecked(capacity, 1);
178-
let p = std::alloc::alloc_zeroed(layout);
179-
let _ = mlock(p, capacity);
180-
CryptoVec {
181-
p,
182-
capacity,
183-
size: 0,
184-
}
226+
if capacity == 0 {
227+
return CryptoVec::default();
228+
}
229+
230+
let capacity = checked_capacity(capacity);
231+
let p = unsafe { alloc_zeroed(capacity) };
232+
CryptoVec {
233+
p,
234+
capacity,
235+
size: 0,
185236
}
186237
}
187238

@@ -220,29 +271,21 @@ impl CryptoVec {
220271
} else {
221272
// realloc ! and erase the previous memory.
222273
unsafe {
223-
let next_capacity = size.next_power_of_two();
274+
let next_capacity = checked_capacity(size);
224275
let old_ptr = self.p;
225-
let next_layout = std::alloc::Layout::from_size_align_unchecked(next_capacity, 1);
226-
self.p = std::alloc::alloc_zeroed(next_layout);
227-
let _ = mlock(self.p, next_capacity);
276+
let next_ptr = alloc_zeroed(next_capacity);
228277

229278
if self.capacity > 0 {
230-
std::ptr::copy_nonoverlapping(old_ptr, self.p, self.size);
279+
std::ptr::copy_nonoverlapping(old_ptr, next_ptr, self.size);
231280
zeroize(old_ptr, self.size);
232281
let _ = munlock(old_ptr, self.capacity);
233282
let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 1);
234283
std::alloc::dealloc(old_ptr, layout);
235284
}
236285

237-
if self.p.is_null() {
238-
#[allow(clippy::panic)]
239-
{
240-
panic!("Realloc failed, pointer = {self:?} {size:?}")
241-
}
242-
} else {
243-
self.capacity = next_capacity;
244-
self.size = size;
245-
}
286+
self.p = next_ptr;
287+
self.capacity = next_capacity;
288+
self.size = size;
246289
}
247290
}
248291
}
@@ -262,7 +305,7 @@ impl CryptoVec {
262305
/// Append a new byte at the end of this CryptoVec.
263306
pub fn push(&mut self, s: u8) {
264307
let size = self.size;
265-
self.resize(size + 1);
308+
self.resize(checked_len_sum(size, 1));
266309
unsafe { *self.p.add(size) = s }
267310
}
268311

@@ -274,7 +317,8 @@ impl CryptoVec {
274317
mut r: R,
275318
) -> Result<usize, std::io::Error> {
276319
let cur_size = self.size;
277-
self.resize(cur_size + n_bytes);
320+
let target_size = checked_len_sum(cur_size, n_bytes);
321+
self.resize(target_size);
278322
let s = unsafe { std::slice::from_raw_parts_mut(self.p.add(cur_size), n_bytes) };
279323
// Resize the buffer to its appropriate size.
280324
match r.read(s) {
@@ -319,7 +363,7 @@ impl CryptoVec {
319363
/// ```
320364
pub fn resize_mut(&mut self, n: usize) -> &mut [u8] {
321365
let size = self.size;
322-
self.resize(size + n);
366+
self.resize(checked_len_sum(size, n));
323367
unsafe { std::slice::from_raw_parts_mut(self.p.add(size), n) }
324368
}
325369

@@ -331,7 +375,8 @@ impl CryptoVec {
331375
/// ```
332376
pub fn extend(&mut self, s: &[u8]) {
333377
let size = self.size;
334-
self.resize(size + s.len());
378+
let added = s.len();
379+
self.resize(checked_len_sum(size, added));
335380
unsafe {
336381
std::ptr::copy_nonoverlapping(s.as_ptr(), self.p.add(size), s.len());
337382
}
@@ -438,7 +483,7 @@ fn optimization_barrier(dst: *mut u8, size: usize) {
438483

439484
#[cfg(test)]
440485
mod test {
441-
use super::CryptoVec;
486+
use super::{CryptoVec, checked_capacity};
442487

443488
#[test]
444489
fn test_new() {
@@ -569,13 +614,39 @@ mod test {
569614
assert!(crypto_vec.is_empty());
570615
}
571616

617+
#[test]
618+
fn test_with_capacity_zero() {
619+
let crypto_vec = CryptoVec::with_capacity(0);
620+
assert_eq!(crypto_vec.size, 0);
621+
assert_eq!(crypto_vec.capacity, 0);
622+
}
623+
624+
#[test]
625+
fn test_new_zeroed_zero() {
626+
let crypto_vec = CryptoVec::new_zeroed(0);
627+
assert_eq!(crypto_vec.size, 0);
628+
assert_eq!(crypto_vec.capacity, 0);
629+
}
630+
572631
#[test]
573632
fn test_extend() {
574633
let mut crypto_vec = CryptoVec::new();
575634
crypto_vec.extend(b"test");
576635
assert_eq!(crypto_vec.as_ref(), b"test");
577636
}
578637

638+
#[test]
639+
#[should_panic(expected = "CryptoVec capacity overflow")]
640+
fn test_checked_capacity_overflow_panics() {
641+
let _ = checked_capacity(usize::MAX);
642+
}
643+
644+
#[test]
645+
#[should_panic(expected = "CryptoVec capacity overflow")]
646+
fn test_checked_capacity_rejects_values_above_max_capacity() {
647+
let _ = checked_capacity(super::MAX_CAPACITY + 1);
648+
}
649+
579650
#[test]
580651
fn test_write_all_from() {
581652
let mut crypto_vec = CryptoVec::new();

cryptovec/src/platform/unix.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ use super::MemoryLockError;
77

88
/// Unlock memory on drop for Unix-based systems.
99
pub fn munlock(ptr: *const u8, len: usize) -> Result<(), MemoryLockError> {
10+
if len == 0 {
11+
return Ok(());
12+
}
13+
let Some(ptr) = NonNull::new(ptr as *mut c_void) else {
14+
return Err(MemoryLockError::new("munlock: null pointer".into()));
15+
};
1016
unsafe {
1117
Errno::clear();
12-
let ptr = NonNull::new_unchecked(ptr as *mut c_void);
1318
nix::sys::mman::munlock(ptr, len).map_err(|e| {
1419
MemoryLockError::new(format!("munlock: {} (0x{:x})", e.desc(), e as i32))
1520
})?;
@@ -18,9 +23,14 @@ pub fn munlock(ptr: *const u8, len: usize) -> Result<(), MemoryLockError> {
1823
}
1924

2025
pub fn mlock(ptr: *const u8, len: usize) -> Result<(), MemoryLockError> {
26+
if len == 0 {
27+
return Ok(());
28+
}
29+
let Some(ptr) = NonNull::new(ptr as *mut c_void) else {
30+
return Err(MemoryLockError::new("mlock: null pointer".into()));
31+
};
2132
unsafe {
2233
Errno::clear();
23-
let ptr = NonNull::new_unchecked(ptr as *mut c_void);
2434
nix::sys::mman::mlock(ptr, len)
2535
.map_err(|e| MemoryLockError::new(format!("mlock: {} (0x{:x})", e.desc(), e as i32)))?;
2636
}

russh/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "Apache-2.0"
99
name = "russh"
1010
readme = "../README.md"
1111
repository = "https://github.com/warp-tech/russh"
12-
version = "0.60.2"
12+
version = "0.60.3"
1313
rust-version = "1.85"
1414

1515
[features]
@@ -91,7 +91,7 @@ rand_core = { version = "0.10.0" }
9191
rand.workspace = true
9292
ring = { version = "0.17.14", optional = true }
9393
rsa = { version = "0.10.0-rc.18", optional = true }
94-
russh-cryptovec = { version = "0.59.0", path = "../cryptovec", features = [
94+
russh-cryptovec = { version = "0.60.3", path = "../cryptovec", features = [
9595
"ssh-encoding",
9696
] }
9797
russh-util = { version = "0.52.0", path = "../russh-util" }

russh/src/keys/agent/client.rs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub trait AgentStream: AsyncRead + AsyncWrite {}
1717

1818
impl<S: AsyncRead + AsyncWrite> AgentStream for S {}
1919

20+
const MAX_AGENT_FRAME_LEN: usize = 256 * 1024;
21+
2022
/// SSH agent client.
2123
pub struct AgentClient<S: AgentStream> {
2224
stream: S,
@@ -112,25 +114,29 @@ impl AgentClient<tokio::net::windows::named_pipe::NamedPipeClient> {
112114
}
113115

114116
impl<S: AgentStream + Unpin> AgentClient<S> {
115-
async fn read_response(&mut self) -> Result<(), Error> {
116-
// Writing the message
117-
self.stream.write_all(&self.buf).await?;
118-
self.stream.flush().await?;
119-
120-
// Reading the length
117+
async fn read_frame(&mut self) -> Result<(), Error> {
121118
self.buf.clear();
122119
self.buf.resize(4, 0);
123120
self.stream.read_exact(&mut self.buf).await?;
124121

125-
// Reading the rest of the buffer
126122
let len = BigEndian::read_u32(&self.buf) as usize;
123+
if len > MAX_AGENT_FRAME_LEN {
124+
return Err(Error::AgentProtocolError);
125+
}
126+
127127
self.buf.clear();
128128
self.buf.resize(len, 0);
129129
self.stream.read_exact(&mut self.buf).await?;
130-
131130
Ok(())
132131
}
133132

133+
async fn read_response(&mut self) -> Result<(), Error> {
134+
// Writing the message
135+
self.stream.write_all(&self.buf).await?;
136+
self.stream.flush().await?;
137+
self.read_frame().await
138+
}
139+
134140
async fn read_success(&mut self) -> Result<(), Error> {
135141
self.read_response().await?;
136142
if self.buf.first() == Some(&msg::SUCCESS) {
@@ -570,3 +576,42 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
570576
}
571577
}
572578
}
579+
580+
#[cfg(test)]
581+
mod tests {
582+
use byteorder::{BigEndian, ByteOrder};
583+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
584+
585+
use super::{AgentClient, MAX_AGENT_FRAME_LEN};
586+
use crate::keys::Error;
587+
588+
#[test]
589+
fn oversized_agent_response_is_rejected_before_allocation() -> std::io::Result<()> {
590+
let runtime = tokio::runtime::Builder::new_current_thread()
591+
.enable_all()
592+
.build()?;
593+
594+
runtime.block_on(async {
595+
let (mut writer, reader) = tokio::io::duplex(64);
596+
let server = tokio::spawn(async move {
597+
let mut frame = [0u8; 4];
598+
writer.read_exact(&mut frame).await?;
599+
let len = BigEndian::read_u32(&frame) as usize;
600+
let mut body = vec![0; len];
601+
writer.read_exact(&mut body).await?;
602+
603+
BigEndian::write_u32(&mut frame, (MAX_AGENT_FRAME_LEN + 1) as u32);
604+
writer.write_all(&frame).await?;
605+
Ok::<(), std::io::Error>(())
606+
});
607+
608+
let mut client = AgentClient::connect(reader);
609+
let err = client.request_identities().await.unwrap_err();
610+
assert!(matches!(err, Error::AgentProtocolError));
611+
server.await.expect("server task")?;
612+
Ok::<(), std::io::Error>(())
613+
})?;
614+
615+
Ok(())
616+
}
617+
}

0 commit comments

Comments
 (0)