From 5c461f795ec1fd92732e9d996842099ee070ffa0 Mon Sep 17 00:00:00 2001 From: 5225225 <5225225@mailbox.org> Date: Thu, 11 Nov 2021 17:16:09 +0000 Subject: [PATCH 1/4] Fix subtraction with overflow with malformed ANSI escape --- src/lib.rs | 15 +++++++++++++++ src/utils.rs | 40 ++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 977f58d306..813d074d4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1061,4 +1061,19 @@ mod tests { assert!(table.print_html(&mut writer).is_ok()); assert_eq!(writer.as_string().replace("\r\n", "\n"), out); } + + #[test] + fn test_panic() { + let mut table = Table::new(); + + table.add_row(Row::new(vec![Cell::new("\u{1b}[\u{1b}\u{0}\u{0}")])); + + let out = "+--+ +| \u{1b}[\u{1b}\u{0}\u{0} | ++--+ +"; + + assert_eq!(table.to_string().replace("\r\n", "\n"), out); + assert_eq!(3, table.print(&mut StringWriter::new()).unwrap()); + } } diff --git a/src/utils.rs b/src/utils.rs index a3ce2ef8a3..eaa5a21435 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use std::fmt; use std::io::{Error, ErrorKind, Write}; use std::str; -use unicode_width::UnicodeWidthStr; +use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use super::format::Alignment; @@ -76,33 +76,53 @@ pub fn print_align(out: &mut T, Ok(()) } + /// Return the display width of a unicode string. /// This functions takes ANSI-escaped color codes into account. pub fn display_width(text: &str) -> usize { + #[derive(PartialEq, Eq, Clone, Copy)] + enum State { + /// We are not inside any terminal escape. + Normal, + /// We have just seen a \u{1b} + EscapeChar, + /// We have just seen a [ + OpenBracket, + /// We just ended the escape by seeing an m + AfterEscape, + } + dbg!(text); + + let width = UnicodeWidthStr::width(text); - let mut state = 0; + let mut state = State::Normal; let mut hidden = 0; for c in text.chars() { state = match (state, c) { - (0, '\u{1b}') => 1, - (1, '[') => 2, - (1, _) => 0, - (2, 'm') => 3, + (State::Normal, '\u{1b}') => State::EscapeChar, + (State::EscapeChar, '[') => State::OpenBracket, + (State::EscapeChar, _) => State::Normal, + (State::OpenBracket, 'm') => State::AfterEscape, _ => state, }; // We don't count escape characters as hidden as // UnicodeWidthStr::width already considers them. - if state > 1 { - hidden += 1; + if matches!(state, State::OpenBracket | State::AfterEscape) { + // but if we see an escape char *inside* the ANSI escape, we should ignore it. + if UnicodeWidthChar::width(c).unwrap_or(0) > 0 { + hidden += 1; + } } - if state == 3 { - state = 0; + if state == State::AfterEscape { + state = State::Normal; } } + dbg!(width, hidden); + width - hidden } From 0e3fff9cd708dcfa1f3fcf4378281523d8160710 Mon Sep 17 00:00:00 2001 From: 5225225 <5225225@mailbox.org> Date: Thu, 11 Nov 2021 17:17:13 +0000 Subject: [PATCH 2/4] Add fuzzing target --- fuzz/.gitignore | 3 + fuzz/Cargo.lock | 267 ++++++++++++++++++++++++ fuzz/Cargo.toml | 25 +++ fuzz/fuzz_targets/render_single_cell.rs | 14 ++ 4 files changed, 309 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.lock create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/render_single_cell.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000000..a0925114d6 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000000..e30a66ed9c --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,267 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577b08a4acd7b99869f863c50011b01eb73424ccc798ecd996f2e24817adfca7" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "prettytable-rs" +version = "0.8.0" +dependencies = [ + "atty", + "csv", + "encode_unicode", + "lazy_static", + "term", + "unicode-width", +] + +[[package]] +name = "prettytable-rs-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "prettytable-rs", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "term" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +dependencies = [ + "dirs", + "winapi", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000000..f089fc84e9 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "prettytable-rs-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.prettytable-rs] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "render_single_cell" +path = "fuzz_targets/render_single_cell.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/render_single_cell.rs b/fuzz/fuzz_targets/render_single_cell.rs new file mode 100644 index 0000000000..f34bcb6e0e --- /dev/null +++ b/fuzz/fuzz_targets/render_single_cell.rs @@ -0,0 +1,14 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use prettytable::{Table, Row, Cell}; + +fuzz_target!(|data: Vec>| { + let mut pt = prettytable::Table::new(); + for row in data { + let cells = row.into_iter().map(|x| Cell::new(&x)).collect(); + pt.add_row(Row::new(cells)); + } + + let _ = pt.print(&mut std::io::sink()); +}); From 29d69104b947ddb3d9087b762acb6a80b729e524 Mon Sep 17 00:00:00 2001 From: 5225225 <5225225@mailbox.org> Date: Thu, 11 Nov 2021 17:18:33 +0000 Subject: [PATCH 3/4] Add assertion to avoid silent overflow --- src/utils.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index eaa5a21435..f876735c75 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -91,8 +91,6 @@ pub fn display_width(text: &str) -> usize { /// We just ended the escape by seeing an m AfterEscape, } - dbg!(text); - let width = UnicodeWidthStr::width(text); let mut state = State::Normal; @@ -121,7 +119,7 @@ pub fn display_width(text: &str) -> usize { } } - dbg!(width, hidden); + assert!(width >= hidden, "internal error: width {} less than hidden {} on string {:?}", width, hidden, text); width - hidden } From 8d28e14049ef455918507bfa3ea402793d0f99be Mon Sep 17 00:00:00 2001 From: 5225225 <5225225@mailbox.org> Date: Thu, 11 Nov 2021 17:29:20 +0000 Subject: [PATCH 4/4] Rename fuzzer --- fuzz/Cargo.toml | 4 ++-- fuzz/fuzz_targets/render.rs | 23 +++++++++++++++++++++++ fuzz/fuzz_targets/render_single_cell.rs | 14 -------------- 3 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 fuzz/fuzz_targets/render.rs delete mode 100644 fuzz/fuzz_targets/render_single_cell.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f089fc84e9..01f8cf504d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -19,7 +19,7 @@ path = ".." members = ["."] [[bin]] -name = "render_single_cell" -path = "fuzz_targets/render_single_cell.rs" +name = "render" +path = "fuzz_targets/render.rs" test = false doc = false diff --git a/fuzz/fuzz_targets/render.rs b/fuzz/fuzz_targets/render.rs new file mode 100644 index 0000000000..7835f05c62 --- /dev/null +++ b/fuzz/fuzz_targets/render.rs @@ -0,0 +1,23 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use prettytable::{Table, Row, Cell}; + +use prettytable::format::Alignment::{self, *}; + +fuzz_target!(|data: Vec>| { + fn align_from_u8(x: u8) -> Alignment { + match x { + 0 => LEFT, + 1 => CENTER, + _ => RIGHT, + } + } + let mut pt = prettytable::Table::new(); + for row in data { + let cells = row.into_iter().map(|x| Cell::new_align(&x.0, align_from_u8(x.1))).collect(); + pt.add_row(Row::new(cells)); + } + + let _ = pt.print(&mut std::io::sink()); +}); diff --git a/fuzz/fuzz_targets/render_single_cell.rs b/fuzz/fuzz_targets/render_single_cell.rs deleted file mode 100644 index f34bcb6e0e..0000000000 --- a/fuzz/fuzz_targets/render_single_cell.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_main] -use libfuzzer_sys::fuzz_target; - -use prettytable::{Table, Row, Cell}; - -fuzz_target!(|data: Vec>| { - let mut pt = prettytable::Table::new(); - for row in data { - let cells = row.into_iter().map(|x| Cell::new(&x)).collect(); - pt.add_row(Row::new(cells)); - } - - let _ = pt.print(&mut std::io::sink()); -});