Skip to content

Commit 34514e2

Browse files
authored
fix: Preserve vt100 cell byte counts (#13058)
## Why Fixes Linear issue VULN-11597. `turborepo-vt100` could store more than 15 UTF-8 bytes in a cell while reading the byte count through a 4-bit mask, allowing `Cell::contents()` to slice in the middle of a multi-byte codepoint. ## What Preserve the full stored byte count for cell contents and replace unchecked UTF-8 conversion with checked decoding that treats invalid contents as an unreachable invariant violation. ## How Added regression coverage for a single cell containing one 3-byte base codepoint plus five 3-byte combining marks. Verified with: - `cargo fmt --check` - `cargo test -p turborepo-vt100` - `cargo clippy -p turborepo-vt100 --all-targets --all-features` Pre-push hooks also ran successfully during `git push`.
1 parent 24e2d34 commit 34514e2

2 files changed

Lines changed: 13 additions & 3 deletions

File tree

crates/turborepo-vt100/src/cell.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl Cell {
4747

4848
#[inline]
4949
fn num_bytes(&self) -> usize {
50-
usize::from(self.num_bytes & 0x0f)
50+
usize::from(self.num_bytes)
5151
}
5252

5353
pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) {
@@ -121,8 +121,9 @@ impl Cell {
121121
#[must_use]
122122
pub fn contents(&self) -> &str {
123123
let num_bytes = self.num_bytes();
124-
// Since contents has been constructed by appending chars encoded as UTF-8 it will be valid UTF-8
125-
unsafe { std::str::from_utf8_unchecked(&self.contents[..num_bytes]) }
124+
std::str::from_utf8(&self.contents[..num_bytes]).unwrap_or_else(
125+
|_| unreachable!("cell contents should be valid UTF-8"),
126+
)
126127
}
127128

128129
/// Returns whether the cell contains any text data.

crates/turborepo-vt100/tests/basic.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ fn cell_contents() {
9494
assert_eq!(parser.screen().cell(0, 6).unwrap().contents(), "");
9595
}
9696

97+
#[test]
98+
fn cell_contents_handles_more_than_15_bytes() {
99+
let mut parser = vt100::Parser::default();
100+
let input = "\u{0800}\u{20d0}\u{20d1}\u{20d2}\u{20d3}\u{20d4}";
101+
parser.process(input.as_bytes());
102+
103+
assert_eq!(parser.screen().cell(0, 0).unwrap().contents(), input);
104+
}
105+
97106
#[test]
98107
fn cell_colors() {
99108
let mut parser = vt100::Parser::default();

0 commit comments

Comments
 (0)