redirect_uri.rs 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. //! Fuzz `redirect_uri` matching (`protocol::redirect_repo`, RFC 3986 §6.2.1
  2. //! plus the RFC 8252 §7.3 loopback exception).
  3. //!
  4. //! Both the requested URI (first input line) and the registered catalog set
  5. //! (following lines, capped) are hostile input: the matcher must never panic,
  6. //! never fall back to prefix/wildcard comparison, and only report `Exact`
  7. //! for a character-for-character member of the registered set.
  8. #![no_main]
  9. use d_oauth::protocol::redirect_repo::{RedirectDecision, match_redirect_uri};
  10. use libfuzzer_sys::fuzz_target;
  11. /// Maximum registered URIs taken from one input; matching is linear, so the
  12. /// cap keeps a single execution bounded.
  13. const MAX_REGISTERED: usize = 8;
  14. fuzz_target!(|data: &[u8]| {
  15. let Ok(text) = core::str::from_utf8(data) else {
  16. return;
  17. };
  18. let mut lines = text.split('\n');
  19. let Some(requested) = lines.next() else {
  20. return;
  21. };
  22. let registered: Vec<&str> = lines.take(MAX_REGISTERED).collect();
  23. // Flag off is the default policy; flag on additionally admits the exact
  24. // host `localhost` as a loopback host. Both must hold the same invariants.
  25. let decision = match_redirect_uri(&registered, requested, false);
  26. let localhost_decision = match_redirect_uri(&registered, requested, true);
  27. // An empty catalog can never match.
  28. if registered.is_empty() {
  29. debug_assert_eq!(decision, RedirectDecision::Reject);
  30. debug_assert_eq!(localhost_decision, RedirectDecision::Reject);
  31. }
  32. // `Exact` fires only for a character-for-character registered member.
  33. for decision in [decision, localhost_decision] {
  34. if decision == RedirectDecision::Exact {
  35. debug_assert!(registered.contains(&requested));
  36. }
  37. }
  38. });