Skip to content

Commit 91fbe99

Browse files
author
Andreas Auernhammer
committed
check for panic during drop
This commit replaces the manual panic tracking with `std::thread::panicking()`. This eliminates the need for the `debug_panic` feature which is also removed by this commit. In addition this commit fixes the `write_buffer` implementations to set the `errored` flag correctly. In particular, the `errored` flag is now set when no more nonces are available, en/decrypting fails or when writing to the underlying writer fails. A new set of unit tests ensures that `EncWriter` and `DecWriter` behave as expected when not closed.
1 parent d0ec255 commit 91fbe99

File tree

4 files changed

+250
-161
lines changed

4 files changed

+250
-161
lines changed

Cargo.toml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,7 @@ overflow-checks = false
2121
default = ["c20p1305"]
2222

2323
c20p1305 = ["ring"]
24-
2524
aesgcm = ["ring"]
2625

27-
# The 'debug_panic' feature prevents panicking when
28-
# an 'EncWriter' or 'DecWriter' gets dropped without
29-
# being closed explicitly - in debug mode. It has no
30-
# effect in release mode. The purpose of this feature
31-
# is to debug a panic caused by some other code. If
32-
# that panic occurs while an 'EncWriter' or 'DecWriter'
33-
# has not been closed (yet) then the close call will be
34-
# skipped but the writer may get dropped (unwinding) -
35-
# which then causes another panic.
36-
#
37-
# This feature should only be enabled when debugging a
38-
# panic.
39-
debug_panic = []
40-
41-
4226
[dependencies]
4327
ring = { version = "0.14.6", optional = true }

src/lib.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@
2424
//! <td>Use <a href="https://briansmith.org/rustdoc/ring/"><code>ring</code></a> to provide
2525
//! default implementation of AES-256-GCM based on Google's <a href="https://github.com/google/boringssl">BoringSSL</a>
2626
//! by implementing the <code>Algorithm</code> trait.
27-
//! <tr><td><code>debug_panic</code>
28-
//! <td>This feature only affects debug builds and should only be enabled when debugging a
29-
//! panic. Both, <code>EncWriter</code> and <code>DecWriter</code> must be closed explicitly.
30-
//! Otherwise, dropping them causes a panic. Take a look at the <code>Close</code> trait for
31-
//! more details. When this feature is enabled, dropping an <code>EncWriter</code> or
32-
//! <code>DecWriter</code> without closing it explicitly does not trigger a panic in debug mode.
33-
//! This may be useful when debugging a panic.
3427
//! </table>
3528
//!
3629
//! # Introduction

src/writer.rs

Lines changed: 121 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use super::aead::Counter;
66
use super::{Aad, Algorithm, Invalid, Key, Nonce, BUF_SIZE, MAX_BUF_SIZE};
77
use std::io;
88
use std::io::Write;
9+
use std::thread::panicking;
910

1011
/// Wraps a writer and encrypts and authenticates everything written to it.
1112
///
@@ -72,11 +73,6 @@ pub struct EncWriter<A: Algorithm, W: Write + internal::Close> {
7273
// EncWriter again. This flag tells the Drop impl if it should skip the
7374
// close.
7475
closed: bool,
75-
76-
// If the inner writer panics in a call to write, we don't want to
77-
// write the buffered data a second time in EncWriter's destructor. This
78-
// flag tells the Drop impl if it should skip the close.
79-
panicked: bool,
8076
}
8177

8278
impl<A: Algorithm, W: Write + internal::Close> EncWriter<A, W> {
@@ -200,7 +196,6 @@ impl<A: Algorithm, W: Write + internal::Close> EncWriter<A, W> {
200196
aad: associated_data,
201197
errored: false,
202198
closed: false,
203-
panicked: false,
204199
})
205200
}
206201

@@ -218,16 +213,33 @@ impl<A: Algorithm, W: Write + internal::Close> EncWriter<A, W> {
218213
/// Encrypt and authenticate the buffer and write the ciphertext
219214
/// to the inner writer.
220215
fn write_buffer(&mut self, len: usize) -> io::Result<()> {
221-
let ciphertext = self.algorithm.seal_in_place(
222-
self.nonce.next()?,
216+
let nonce = match self.nonce.next() {
217+
Ok(nonce) => nonce,
218+
Err(err) => {
219+
self.errored = true;
220+
return Err(err.into());
221+
}
222+
};
223+
224+
let ciphertext = match self.algorithm.seal_in_place(
225+
nonce,
223226
&self.aad,
224227
&mut self.buffer[..len + A::TAG_LEN],
225-
)?;
228+
) {
229+
Ok(ciphertext) => ciphertext,
230+
Err(err) => {
231+
self.errored = true;
232+
return Err(err.into());
233+
}
234+
};
226235

227-
self.panicked = true;
228-
let r = self.inner.write_all(ciphertext);
229-
self.panicked = false;
230-
r
236+
match self.inner.write_all(ciphertext) {
237+
Ok(v) => Ok(v),
238+
Err(err) => {
239+
self.errored = true;
240+
Err(err)
241+
}
242+
}
231243
}
232244
}
233245

@@ -237,36 +249,32 @@ impl<A: Algorithm, W: Write + internal::Close> Write for EncWriter<A, W> {
237249
return Err(io::Error::from(io::ErrorKind::Other));
238250
}
239251

240-
let r: io::Result<usize> = {
241-
let n = buf.len();
242-
243-
let remaining = self.buf_size - self.pos;
244-
if buf.len() <= remaining {
245-
self.buffer[self.pos..self.pos + buf.len()].copy_from_slice(buf);
246-
self.pos += buf.len();
247-
return Ok(buf.len());
248-
}
252+
let n = buf.len();
253+
let remaining = self.buf_size - self.pos;
254+
if n <= remaining {
255+
self.buffer[self.pos..self.pos + n].copy_from_slice(buf);
256+
self.pos += n;
257+
return Ok(n);
258+
}
249259

250-
self.buffer[self.pos..self.buf_size].copy_from_slice(&buf[..remaining]);
251-
self.write_buffer(self.buf_size)?;
252-
self.pos = 0;
253-
254-
let buf = &buf[remaining..];
255-
let chunks = buf.chunks(self.buf_size);
256-
chunks
257-
.clone()
258-
.take(chunks.len() - 1) // Since we take only n-1 elements...
259-
.try_for_each(|chunk| {
260-
self.buffer[..self.buf_size].copy_from_slice(chunk);
261-
self.write_buffer(self.buf_size)
262-
})?;
263-
let last = chunks.last().unwrap(); // ... thereis always a last one.
264-
self.buffer[..last.len()].copy_from_slice(last); // ... there is always a last one.
265-
self.pos = last.len();
266-
Ok(n)
267-
};
268-
self.errored = r.is_err();
269-
r
260+
self.buffer[self.pos..self.buf_size].copy_from_slice(&buf[..remaining]);
261+
self.write_buffer(self.buf_size)?;
262+
self.pos = 0;
263+
let buf = &buf[remaining..];
264+
265+
let chunks = buf.chunks(self.buf_size);
266+
chunks
267+
.clone()
268+
.take(chunks.len() - 1) // Since we take only n-1 elements...
269+
.try_for_each(|chunk| {
270+
self.buffer[..self.buf_size].copy_from_slice(chunk);
271+
self.write_buffer(self.buf_size)
272+
})?;
273+
274+
let last = chunks.last().unwrap(); // ... thereis always a last one.
275+
self.buffer[..last.len()].copy_from_slice(last); // ... there is always a last one.
276+
self.pos = last.len();
277+
Ok(n)
270278
}
271279

272280
#[inline]
@@ -278,10 +286,7 @@ impl<A: Algorithm, W: Write + internal::Close> Write for EncWriter<A, W> {
278286
if self.errored {
279287
return Err(io::Error::from(io::ErrorKind::Other));
280288
}
281-
self.panicked = true;
282289
let r = self.inner.flush();
283-
self.panicked = false;
284-
285290
self.errored = r.is_err();
286291
r
287292
}
@@ -302,29 +307,16 @@ impl<A: Algorithm, W: Write + internal::Close> internal::Close for EncWriter<A,
302307

303308
impl<A: Algorithm, W: Write + internal::Close> Drop for EncWriter<A, W> {
304309
fn drop(&mut self) {
305-
// We must not check whether the EncWriter has been closed if:
306-
// - a inner write or flush call panic'd.
307-
// - we encountered an error during a write or flush call.
308-
if !self.panicked && !self.errored {
309-
// For debugging purposes, we allow disabling the panic
310-
// for debug builds - but only if the feature "debug_panic"
311-
// is turned on.
312-
if !(cfg!(debug_assertions) && cfg!(feature = "debug_panic")) {
313-
// Actually, Drop implementations should not panic.
314-
// However, not closing the EncWriter (see: close())
315-
// implies not encrypting the entire plaintext such that
316-
// the ciphertext written to the inner writer cannot be
317-
// decrypted anymore. Consequently, we would "loose" data.
318-
//
319-
// We could call close() here if it hasn't been called explicitly
320-
// by callers but that would only succeed if no other I/O error
321-
// occurs. Otherwise, we are in the same situation as before. Calling
322-
// close() here would be an optimistic approach - while in cryptography
323-
// we have to be pessimistic.
324-
assert!(
325-
self.closed,
326-
"EncWriter must be closed explicitly via the close method before being dropped!"
327-
);
310+
// We must not check whether the EncWriter has been closed if
311+
// we encountered an error during a write or flush call.
312+
if !self.errored {
313+
if !self.closed {
314+
// We don't want to panic again if some code (between
315+
// EncWriter::new(...) and EncWriter.close()) already
316+
// panic'd. Otherwise we would cause a "double-panic".
317+
if !panicking() {
318+
panic!("EncWriter must be closed explicitly via the close method before being dropped!")
319+
}
328320
}
329321
}
330322
}
@@ -396,11 +388,6 @@ pub struct DecWriter<A: Algorithm, W: Write + internal::Close> {
396388
// EncWriter again. This flag tells the Drop impl if it should skip the
397389
// close.
398390
closed: bool,
399-
400-
// If the inner writer panics in a call to write, we don't want to
401-
// write the buffered data a second time in DecWriter's destructor. This
402-
// flag tells the Drop impl if it should skip the close.
403-
panicked: bool,
404391
}
405392

406393
impl<A: Algorithm, W: Write + internal::Close> DecWriter<A, W> {
@@ -529,7 +516,6 @@ impl<A: Algorithm, W: Write + internal::Close> DecWriter<A, W> {
529516
aad: associated_data,
530517
errored: false,
531518
closed: false,
532-
panicked: false,
533519
})
534520
}
535521

@@ -547,14 +533,33 @@ impl<A: Algorithm, W: Write + internal::Close> DecWriter<A, W> {
547533
/// Decrypt and verifies the buffer and write the plaintext
548534
/// to the inner writer.
549535
fn write_buffer(&mut self, len: usize) -> io::Result<()> {
550-
let plaintext =
551-
self.algorithm
552-
.open_in_place(self.nonce.next()?, &self.aad, &mut self.buffer[..len])?;
536+
let nonce = match self.nonce.next() {
537+
Ok(nonce) => nonce,
538+
Err(err) => {
539+
self.errored = true;
540+
return Err(err.into());
541+
}
542+
};
553543

554-
self.panicked = true;
555-
let r = self.inner.write_all(plaintext);
556-
self.panicked = false;
557-
r
544+
let plaintext =
545+
match self
546+
.algorithm
547+
.open_in_place(nonce, &self.aad, &mut self.buffer[..len])
548+
{
549+
Ok(plaintext) => plaintext,
550+
Err(err) => {
551+
self.errored = true;
552+
return Err(err.into());
553+
}
554+
};
555+
556+
match self.inner.write_all(plaintext) {
557+
Ok(v) => Ok(v),
558+
Err(err) => {
559+
self.errored = true;
560+
Err(err)
561+
}
562+
}
558563
}
559564
}
560565

@@ -563,37 +568,33 @@ impl<A: Algorithm, W: Write + internal::Close> Write for DecWriter<A, W> {
563568
if self.errored {
564569
return Err(io::Error::from(io::ErrorKind::Other));
565570
}
566-
let r: io::Result<usize> = {
567-
let n = buf.len();
568-
569-
let remaining = self.buf_size + A::TAG_LEN - self.pos;
570-
if buf.len() <= remaining {
571-
self.buffer[self.pos..self.pos + buf.len()].copy_from_slice(buf);
572-
self.pos += buf.len();
573-
return Ok(n);
574-
}
575571

576-
self.buffer[self.pos..].copy_from_slice(&buf[..remaining]);
577-
self.write_buffer(self.buf_size + A::TAG_LEN)?;
578-
self.pos = 0;
579-
580-
let buf = &buf[remaining..];
581-
let chunks = buf.chunks(self.buf_size + A::TAG_LEN);
582-
chunks
583-
.clone()
584-
.take(chunks.len() - 1) // Since we take only n-1 elements...
585-
.try_for_each(|chunk| {
586-
self.buffer.copy_from_slice(chunk);
587-
self.write_buffer(self.buf_size + A::TAG_LEN)
588-
})?;
589-
let last = chunks.last().unwrap(); // ... there is always a last one.
590-
self.buffer[..last.len()].copy_from_slice(last);
591-
self.pos = last.len();
592-
Ok(n)
593-
};
572+
let n = buf.len();
573+
let remaining = self.buf_size + A::TAG_LEN - self.pos;
574+
if n <= remaining {
575+
self.buffer[self.pos..self.pos + n].copy_from_slice(buf);
576+
self.pos += n;
577+
return Ok(n);
578+
}
594579

595-
self.errored = r.is_err();
596-
r
580+
self.buffer[self.pos..].copy_from_slice(&buf[..remaining]);
581+
self.write_buffer(self.buf_size + A::TAG_LEN)?;
582+
self.pos = 0;
583+
let buf = &buf[remaining..];
584+
585+
let chunks = buf.chunks(self.buf_size + A::TAG_LEN);
586+
chunks
587+
.clone()
588+
.take(chunks.len() - 1) // Since we take only n-1 elements...
589+
.try_for_each(|chunk| {
590+
self.buffer.copy_from_slice(chunk);
591+
self.write_buffer(self.buf_size + A::TAG_LEN)
592+
})?;
593+
594+
let last = chunks.last().unwrap(); // ... there is always a last one.
595+
self.buffer[..last.len()].copy_from_slice(last);
596+
self.pos = last.len();
597+
Ok(n)
597598
}
598599

599600
#[inline]
@@ -605,10 +606,7 @@ impl<A: Algorithm, W: Write + internal::Close> Write for DecWriter<A, W> {
605606
if self.errored {
606607
return Err(io::Error::from(io::ErrorKind::Other));
607608
}
608-
self.panicked = true;
609609
let r = self.inner.flush();
610-
self.panicked = false;
611-
612610
self.errored = r.is_err();
613611
r
614612
}
@@ -629,31 +627,16 @@ impl<A: Algorithm, W: Write + internal::Close> internal::Close for DecWriter<A,
629627

630628
impl<A: Algorithm, W: Write + internal::Close> Drop for DecWriter<A, W> {
631629
fn drop(&mut self) {
632-
// We must not check whether the DecWriter has been closed if:
633-
// - a inner write or flush call panic'd.
634-
// - we encountered an error during a write or flush call.
635-
if !self.panicked && !self.errored {
636-
// For debugging purposes,we allow disabling the panic
637-
// for debug builds - but only if the feature "debug_panic"
638-
// is turned on.
639-
if !(cfg!(debug_assertions) && cfg!(feature = "debug_panic")) {
640-
// Actually, Drop implementations should not panic.
641-
// However, not closing the DecWriter (see: close())
642-
// implies not decrypting the entire ciphertext and
643-
// also not writing the entire plaintext to the inner
644-
// writer. Consequently, not calling close causes not
645-
// authentic (b/c incomplete) plaintext output.
646-
//
647-
// We could call close() here if it hasn't been called explicitly
648-
// by callers but that would only succeed if the ciphertext
649-
// is authentic and no other I/O error occurs. Otherwise, we
650-
// are in the same situation as before. Calling close() here
651-
// would be an optimistic approach - while in cryptography we have
652-
// to be pessimistic.
653-
assert!(
654-
self.closed,
655-
"DecWriter must be closed explicitly via the close method before being dropped!"
656-
);
630+
// We must not check whether the DecWriter has been closed if
631+
// we encountered an error during a write or flush call.
632+
if !self.errored {
633+
if !self.closed {
634+
// We don't want to panic again if some code (between
635+
// DecWriter::new(...) and DecWriter.close()) already
636+
// panic'd. Otherwise we would cause a "double-panic".
637+
if !panicking() {
638+
panic!("DecWriter must be closed explicitly via the close method before being dropped!")
639+
}
657640
}
658641
}
659642
}

0 commit comments

Comments
 (0)