Skip to content

Instantly share code, notes, and snippets.

@CosmicHorrorDev
Created January 13, 2023 01:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CosmicHorrorDev/448e5bf0f1124d5fa7b1291103f62a11 to your computer and use it in GitHub Desktop.
Save CosmicHorrorDev/448e5bf0f1124d5fa7b1291103f62a11 to your computer and use it in GitHub Desktop.
Console windows ansi parsing fuzz target
#![no_main]
use blah::{new, old};
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
assert_eq!(new::output(s), old::output(s));
}
});
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Output {
Color(Intense, Color, FgBg),
NotAttr,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Intense {
Yes,
No,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FgBg {
Foreground,
Background,
}
impl FgBg {
pub fn new(byte: u8) -> Option<Self> {
match byte {
b'3' => Some(Self::Foreground),
b'4' => Some(Self::Background),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
pub mod common;
pub mod new;
pub mod old;
use std::str::Bytes;
use crate::common::{Color, FgBg, Intense, Output};
pub fn output(part: &str) -> Option<Output> {
if let Some((intense, color, fg_bg)) = driver(parse_color, part) {
Some(Output::Color(intense, color, fg_bg))
} else if driver(parse_attr, part).is_none() {
Some(Output::NotAttr)
} else {
None
}
}
fn driver<Out>(parse: fn(Bytes<'_>) -> Option<Out>, part: &str) -> Option<Out> {
let mut bytes = part.bytes();
loop {
while bytes.next()? != b'\x1b' {}
if let ret @ Some(_) = (parse)(bytes.clone()) {
return ret;
}
}
}
// Parses the equivalent of the regex
// \x1b\[(3|4)8;5;(8|9|1[0-5])m
// for intense or
// \x1b\[(3|4)([0-7])m
// for normal
fn parse_color(mut bytes: Bytes<'_>) -> Option<(Intense, Color, FgBg)> {
parse_prefix(&mut bytes)?;
let fg_bg = FgBg::new(bytes.next()?)?;
let (intense, color) = match bytes.next()? {
b @ b'0'..=b'7' => (Intense::No, normal_color_ansi_from_byte(b)?),
b'8' => {
if &[bytes.next()?, bytes.next()?, bytes.next()?] != b";5;" {
return None;
}
(Intense::Yes, parse_intense_color_ansi(&mut bytes)?)
}
_ => return None,
};
parse_suffix(&mut bytes)?;
Some((intense, color, fg_bg))
}
// Parses the equivalent of the regex
// \x1b\[([1-8])m
fn parse_attr(mut bytes: Bytes<'_>) -> Option<u8> {
parse_prefix(&mut bytes)?;
let attr = match bytes.next()? {
attr @ b'1'..=b'8' => attr,
_ => return None,
};
parse_suffix(&mut bytes)?;
Some(attr)
}
fn parse_prefix(bytes: &mut Bytes<'_>) -> Option<()> {
if bytes.next()? == b'[' {
Some(())
} else {
None
}
}
fn parse_intense_color_ansi(bytes: &mut Bytes<'_>) -> Option<Color> {
let color = match bytes.next()? {
b'8' => Color::Black,
b'9' => Color::Red,
b'1' => match bytes.next()? {
b'0' => Color::Green,
b'1' => Color::Yellow,
b'2' => Color::Blue,
b'3' => Color::Magenta,
b'4' => Color::Cyan,
b'5' => Color::White,
_ => return None,
},
_ => return None,
};
Some(color)
}
fn normal_color_ansi_from_byte(b: u8) -> Option<Color> {
let color = match b {
b'0' => Color::Black,
b'1' => Color::Red,
b'2' => Color::Green,
b'3' => Color::Yellow,
b'4' => Color::Blue,
b'5' => Color::Magenta,
b'6' => Color::Cyan,
b'7' => Color::White,
_ => return None,
};
Some(color)
}
fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> {
if bytes.next()? == b'm' {
Some(())
} else {
None
}
}
#[test]
fn fuzz_crash() {
let input = std::str::from_utf8(&[121, 27, 91, 50, 45, 0, 0, 16, 27, 91, 50, 109, 10, 121, 10])
.unwrap();
let output = output(input);
assert!(output.is_none(), "{output:?}");
}
use crate::common::{Color, FgBg, Intense, Output};
use regex::Regex;
lazy_static::lazy_static! {
static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap();
static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap();
static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap();
}
pub fn output(part: &str) -> Option<Output> {
if let Some(cap) = INTENSE_COLOR_RE.captures(part) {
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
let fg_bg = match cap.get(1).unwrap().as_str() {
"3" => FgBg::Foreground,
"4" => FgBg::Background,
_ => unreachable!(),
};
Some(Output::Color(Intense::Yes, color, fg_bg))
} else if let Some(cap) = NORMAL_COLOR_RE.captures(part) {
let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
let fg_bg = match cap.get(1).unwrap().as_str() {
"3" => FgBg::Foreground,
"4" => FgBg::Background,
_ => unreachable!(),
};
Some(Output::Color(Intense::No, color, fg_bg))
} else if !ATTR_RE.is_match(part) {
Some(Output::NotAttr)
} else {
None
}
}
fn get_color_from_ansi(ansi: &str) -> Color {
match ansi {
"0" | "8" => Color::Black,
"1" | "9" => Color::Red,
"2" | "10" => Color::Green,
"3" | "11" => Color::Yellow,
"4" | "12" => Color::Blue,
"5" | "13" => Color::Magenta,
"6" | "14" => Color::Cyan,
"7" | "15" => Color::White,
_ => unreachable!(),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment