summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-11-19 01:00:48 +0000
committerJohn Turner <jturner.usa@gmail.com>2025-11-19 01:00:48 +0000
commit70e8ea24a8ec9e3dfe484227463fdb97ee341227 (patch)
tree1a19f0974b683c87e6e5c791252d5a5b99669b97
parente01637fd3a0a59ffea320a7df9770e73f486c91e (diff)
downloadgentoo-utils-70e8ea24a8ec9e3dfe484227463fdb97ee341227.tar.gz
impl vercmp fuzzer
-rw-r--r--Cargo.toml10
-rw-r--r--fuzz/atom/meson.build1
-rw-r--r--fuzz/atom/vercmp/fuzz.rs112
-rw-r--r--fuzz/atom/vercmp/gencorpus.rs43
-rw-r--r--fuzz/atom/vercmp/meson.build6
-rwxr-xr-xscripts/vercmp.py14
6 files changed, 185 insertions, 1 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 0663f32..a92311c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,4 +18,12 @@ name = "atom_parser_gencorpus"
[[test]]
path = "fuzz/atom/parser/fuzz.rs"
-name = "atom_parser_fuzz" \ No newline at end of file
+name = "atom_parser_fuzz"
+
+[[bin]]
+path = "fuzz/atom/vercmp/gencorpus.rs"
+name = "atom_vercmp_gencorpus"
+
+[[test]]
+path = "fuzz/atom/vercmp/fuzz.rs"
+name = "atom_vercmp_fuzz" \ No newline at end of file
diff --git a/fuzz/atom/meson.build b/fuzz/atom/meson.build
index 57b0131..6822370 100644
--- a/fuzz/atom/meson.build
+++ b/fuzz/atom/meson.build
@@ -1 +1,2 @@
subdir('parser')
+subdir('vercmp')
diff --git a/fuzz/atom/vercmp/fuzz.rs b/fuzz/atom/vercmp/fuzz.rs
new file mode 100644
index 0000000..98483cb
--- /dev/null
+++ b/fuzz/atom/vercmp/fuzz.rs
@@ -0,0 +1,112 @@
+use core::slice;
+use gentoo_utils::{
+ Parseable,
+ atom::{Atom, Version},
+};
+use mon::{Parser, ParserFinishedError, input::InputIter};
+use std::{
+ cmp::Ordering,
+ io::{BufRead, BufReader, Write},
+ process::{ChildStdin, ChildStdout, Command, Stdio},
+ sync::{LazyLock, Mutex},
+};
+
+struct PyProcess {
+ stdin: Mutex<ChildStdin>,
+ stdout: Mutex<BufReader<ChildStdout>>,
+ buffer: Mutex<String>,
+}
+
+#[allow(clippy::missing_safety_doc, clippy::needless_return)]
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn LLVMFuzzerTestOneInput(input: *const u8, len: usize) -> i32 {
+ static PY_PROCESS: LazyLock<PyProcess> = LazyLock::new(|| {
+ #[allow(clippy::zombie_processes)]
+ let mut proc = Command::new("vercmp.py")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .expect("failed to spawn vercmp.py");
+
+ let stdin = Mutex::new(proc.stdin.take().unwrap());
+ let stdout = Mutex::new(BufReader::new(proc.stdout.take().unwrap()));
+
+ PyProcess {
+ stdin,
+ stdout,
+ buffer: Mutex::new(String::new()),
+ }
+ });
+
+ let control = Version::parser()
+ .parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1-8"))
+ .unwrap();
+
+ let slice = unsafe { slice::from_raw_parts(input, len) };
+
+ if slice.iter().any(|b| !b.is_ascii_graphic()) {
+ return -1;
+ }
+
+ let str = match str::from_utf8(slice) {
+ Ok(str) => str,
+ Err(_) => return -1,
+ };
+
+ let version_str = match str.split_ascii_whitespace().nth(0) {
+ Some(lhs) => lhs,
+ None => return -1,
+ };
+
+ dbg!(version_str);
+
+ let version = match Version::parser().parse_finished(InputIter::new(version_str)) {
+ Ok(a) => a,
+ Err(_) => return -1,
+ };
+
+ let gentoo_utils = control.cmp(&version);
+
+ let portage_result = portage_vercmp(&PY_PROCESS, &control, &version);
+
+ match portage_result {
+ Ok(portage) if portage == gentoo_utils => {
+ eprintln!("agreement on {control} cmp {version} == {portage:?}");
+ }
+ Ok(portage) => {
+ panic!(
+ "disagreement on {control} == {version}:\nportage:{portage:?} gentoo_utils:{gentoo_utils:?}"
+ )
+ }
+ Err(_) => {
+ panic!("parsed invalid versions: {control} | {version}")
+ }
+ }
+
+ return 0;
+}
+
+fn portage_vercmp(pyproc: &PyProcess, a: &Version, b: &Version) -> Result<Ordering, ()> {
+ let mut stdin = pyproc.stdin.lock().expect("failed to get stdin lock");
+ let mut stdout = pyproc.stdout.lock().expect("failed to get stdout lock");
+ let mut buffer = pyproc.buffer.lock().expect("failed to get buffer lock");
+
+ writeln!(&mut stdin, "{a} {b}").expect("failed to write line to python process");
+
+ stdin.flush().unwrap();
+
+ buffer.clear();
+
+ stdout
+ .read_line(&mut buffer)
+ .expect("failed to read line from python process");
+
+ match buffer.as_str().trim() {
+ "0" => Ok(Ordering::Equal),
+ "1" => Ok(Ordering::Greater),
+ "-1" => Ok(Ordering::Less),
+ "err" => Err(()),
+ other => panic!("unexpected result from python: {other}"),
+ }
+}
diff --git a/fuzz/atom/vercmp/gencorpus.rs b/fuzz/atom/vercmp/gencorpus.rs
new file mode 100644
index 0000000..6d5eeef
--- /dev/null
+++ b/fuzz/atom/vercmp/gencorpus.rs
@@ -0,0 +1,43 @@
+use std::{
+ env,
+ error::Error,
+ fs::{self, OpenOptions},
+ io::Write,
+ path::PathBuf,
+};
+
+use gentoo_utils::ebuild::repo::Repo;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let corpus_dir = PathBuf::from(
+ env::args()
+ .nth(1)
+ .expect("expected corpus directory as first argument"),
+ );
+
+ fs::create_dir_all(&corpus_dir)?;
+
+ let repo = Repo::new("/var/db/repos/gentoo");
+ let mut versions = Vec::new();
+
+ for category in repo.categories()? {
+ for ebuild in category?.ebuilds()? {
+ let version = ebuild?.version().clone();
+
+ versions.push(version);
+ }
+ }
+
+ for (i, version) in versions.iter().enumerate() {
+ let path = corpus_dir.as_path().join(i.to_string());
+ let mut file = OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .create(true)
+ .open(path)?;
+
+ write!(file, "{version}")?;
+ }
+
+ Ok(())
+}
diff --git a/fuzz/atom/vercmp/meson.build b/fuzz/atom/vercmp/meson.build
new file mode 100644
index 0000000..51b237b
--- /dev/null
+++ b/fuzz/atom/vercmp/meson.build
@@ -0,0 +1,6 @@
+fuzzers += {
+ 'atom_vercmp': [
+ meson.current_source_dir() / 'gencorpus.rs',
+ meson.current_source_dir() / 'fuzz.rs',
+ ],
+}
diff --git a/scripts/vercmp.py b/scripts/vercmp.py
new file mode 100755
index 0000000..f02cb41
--- /dev/null
+++ b/scripts/vercmp.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+import sys
+import portage
+
+for line in sys.stdin.buffer:
+ a, b = line.decode().split(" ")
+
+ try:
+ sys.stdout.buffer.write(f"{portage.vercmp(a, b)}\n".encode())
+ except:
+ sys.stdout.buffer.write(b"err\n")
+ finally:
+ sys.stdout.buffer.flush()