summaryrefslogtreecommitdiff
path: root/fuzz/atom
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-11-18 22:43:22 +0000
committerJohn Turner <jturner.usa@gmail.com>2025-11-18 22:43:22 +0000
commite01637fd3a0a59ffea320a7df9770e73f486c91e (patch)
treefa76152764a7a53a0f6dab21585064e14406a07a /fuzz/atom
parente0cc7f6a03008f623d3286678f6dc22f8cebef51 (diff)
downloadgentoo-utils-e01637fd3a0a59ffea320a7df9770e73f486c91e.tar.gz
setup meson to allow building multiple fuzzers easily
Diffstat (limited to 'fuzz/atom')
-rw-r--r--fuzz/atom/meson.build1
-rw-r--r--fuzz/atom/parser/fuzz.rs100
-rw-r--r--fuzz/atom/parser/gencorpus.rs64
-rw-r--r--fuzz/atom/parser/meson.build6
4 files changed, 171 insertions, 0 deletions
diff --git a/fuzz/atom/meson.build b/fuzz/atom/meson.build
new file mode 100644
index 0000000..57b0131
--- /dev/null
+++ b/fuzz/atom/meson.build
@@ -0,0 +1 @@
+subdir('parser')
diff --git a/fuzz/atom/parser/fuzz.rs b/fuzz/atom/parser/fuzz.rs
new file mode 100644
index 0000000..610e08b
--- /dev/null
+++ b/fuzz/atom/parser/fuzz.rs
@@ -0,0 +1,100 @@
+use core::slice;
+use gentoo_utils::{Parseable, atom::Atom};
+use mon::{Parser, ParserFinishedError, input::InputIter};
+use std::{
+ 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("atom.py")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .expect("failed to spawn atom.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 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 atom = str.trim();
+
+ let mut stdin = PY_PROCESS.stdin.lock().expect("failed to get stdin lock");
+
+ writeln!(&mut stdin, "{atom}").expect("failed to write to python stdin");
+
+ let mut stdout = PY_PROCESS.stdout.lock().expect("failed to get stdout lock");
+
+ let mut buffer = PY_PROCESS.buffer.lock().expect("failed to get buffer lock");
+
+ buffer.clear();
+
+ stdout
+ .read_line(&mut buffer)
+ .expect("failed to readline from python");
+
+ let portage_result = match buffer.as_str().trim() {
+ "0" => true,
+ "1" => false,
+ result => panic!("got unexpected result from python: {result}"),
+ };
+
+ let gentoo_utils_result = Atom::parser().parse_finished(InputIter::new(atom));
+
+ match (portage_result, gentoo_utils_result) {
+ (true, Ok(_)) => {
+ eprintln!("agreement that {atom} is valid");
+ }
+ (false, Err(_)) => {
+ eprintln!("agreement that {atom} is invalid");
+ }
+ (true, Err(_)) => {
+ panic!("rejected valid atom: {atom}");
+ }
+ (false, Ok(atom))
+ if atom.usedeps().iter().any(|usedep| {
+ atom.usedeps()
+ .iter()
+ .filter(|u| usedep.flag() == u.flag())
+ .count()
+ > 1
+ }) =>
+ {
+ eprintln!("disagreement due to duplicates in usedeps");
+ }
+ (false, Ok(_)) => {
+ panic!("accpeted invalid atom: {atom}")
+ }
+ }
+
+ return 0;
+}
diff --git a/fuzz/atom/parser/gencorpus.rs b/fuzz/atom/parser/gencorpus.rs
new file mode 100644
index 0000000..67ba1e3
--- /dev/null
+++ b/fuzz/atom/parser/gencorpus.rs
@@ -0,0 +1,64 @@
+use std::{
+ env,
+ error::Error,
+ fs::{self, OpenOptions},
+ io::Write,
+ path::PathBuf,
+};
+
+use gentoo_utils::{
+ atom::Atom,
+ ebuild::{Depend, 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 atoms = Vec::new();
+
+ for category in repo.categories()? {
+ for ebuild in category?.ebuilds()? {
+ let depend = ebuild?.depend().to_vec();
+
+ for expr in depend {
+ walk_expr(&mut atoms, &expr);
+ }
+ }
+ }
+
+ for (i, atom) in atoms.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, "{atom}")?;
+ }
+
+ Ok(())
+}
+
+fn walk_expr(atoms: &mut Vec<Atom>, depend: &Depend<Atom>) {
+ match depend {
+ Depend::Element(atom) => {
+ atoms.push(atom.clone());
+ }
+ Depend::AllOf(exprs)
+ | Depend::OneOf(exprs)
+ | Depend::AnyOf(exprs)
+ | Depend::ConditionalGroup(_, exprs) => {
+ for expr in exprs {
+ walk_expr(atoms, expr);
+ }
+ }
+ }
+}
diff --git a/fuzz/atom/parser/meson.build b/fuzz/atom/parser/meson.build
new file mode 100644
index 0000000..efc84c6
--- /dev/null
+++ b/fuzz/atom/parser/meson.build
@@ -0,0 +1,6 @@
+fuzzers += {
+ 'atom_parser': [
+ meson.current_source_dir() / 'gencorpus.rs',
+ meson.current_source_dir() / 'fuzz.rs',
+ ],
+}