Skip to content

Commit 083c3c4

Browse files
authored
Fix panic safety issue in StderrForwarder (#1079)
1 parent ca1f5f9 commit 083c3c4

File tree

1 file changed

+24
-23
lines changed

1 file changed

+24
-23
lines changed

src/command_helpers.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ impl StderrForwarder {
9090
}
9191
}
9292

93-
#[allow(clippy::uninit_vec)]
9493
fn forward_available(&mut self) -> bool {
9594
if let Some((stderr, buffer)) = self.inner.as_mut() {
9695
loop {
@@ -104,7 +103,7 @@ impl StderrForwarder {
104103
let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
105104
match crate::parallel::stderr::bytes_available(stderr) {
106105
#[cfg(windows)]
107-
Ok(0) => return false,
106+
Ok(0) => break false,
108107
#[cfg(unix)]
109108
Ok(0) => {
110109
// On Unix, depending on the implementation, we may sometimes get 0 in a
@@ -120,7 +119,7 @@ impl StderrForwarder {
120119
write_warning(&buffer[..]);
121120
}
122121
self.inner = None;
123-
return true;
122+
break true;
124123
}
125124
#[cfg(unix)]
126125
Err(_) => {
@@ -137,32 +136,21 @@ impl StderrForwarder {
137136
};
138137
buffer.reserve(to_reserve);
139138

140-
// SAFETY: 1) the length is set to the capacity, so we are never using memory beyond
141-
// the underlying buffer and 2) we always call `truncate` below to set the len back
142-
// to the initialized data.
143-
unsafe {
144-
buffer.set_len(buffer.capacity());
145-
}
146-
match stderr.read(&mut buffer[old_data_end..]) {
139+
// Safety: stderr.read only writes to the spare part of the buffer, it never reads from it
140+
match stderr
141+
.read(unsafe { &mut *(buffer.spare_capacity_mut() as *mut _ as *mut [u8]) })
142+
{
147143
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
148144
// No data currently, yield back.
149-
buffer.truncate(old_data_end);
150-
return false;
145+
break false;
151146
}
152147
Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
153148
// Interrupted, try again.
154-
buffer.truncate(old_data_end);
155-
}
156-
Ok(0) | Err(_) => {
157-
// End of stream: flush remaining data and bail.
158-
if old_data_end > 0 {
159-
write_warning(&buffer[..old_data_end]);
160-
}
161-
self.inner = None;
162-
return true;
149+
continue;
163150
}
164-
Ok(bytes_read) => {
165-
buffer.truncate(old_data_end + bytes_read);
151+
Ok(bytes_read) if bytes_read != 0 => {
152+
// Safety: bytes_read bytes is written to spare part of the buffer
153+
unsafe { buffer.set_len(old_data_end + bytes_read) };
166154
let mut consumed = 0;
167155
for line in buffer.split_inclusive(|&b| b == b'\n') {
168156
// Only forward complete lines, leave the rest in the buffer.
@@ -173,6 +161,19 @@ impl StderrForwarder {
173161
}
174162
buffer.drain(..consumed);
175163
}
164+
res => {
165+
// End of stream: flush remaining data and bail.
166+
if old_data_end > 0 {
167+
write_warning(&buffer[..old_data_end]);
168+
}
169+
if let Err(err) = res {
170+
write_warning(
171+
format!("Failed to read from child stderr: {err}").as_bytes(),
172+
);
173+
}
174+
self.inner.take();
175+
break true;
176+
}
176177
}
177178
}
178179
} else {

0 commit comments

Comments
 (0)