diff --git a/crates/lensdb/src/lib.rs b/crates/lensdb/src/lib.rs
--- a/crates/lensdb/src/lib.rs
+++ b/crates/lensdb/src/lib.rs
@@ -159,17 +159,24 @@ impl LensDb {
 
     /// The camera with this EXIF make and model.
     pub fn find_camera(&self, make: &str, model: &str) -> Option<&Camera> {
-        let (mk, md) = (tokens(make), tokens(model));
+        let mk = tokens(make);
+        let raw = tokens(model);
+        let md = strip_maker(raw.clone(), &mk);
         if md.is_empty() {
             return None;
         }
-        self.cameras.iter().find(|c| tokens(&c.model) == md && maker_matches(&tokens(&c.maker), &mk))
+        self.cameras.iter().find(|c| {
+            let cm = tokens(&c.maker);
+            // with no EXIF make, the model may still start with the maker's word
+            let md = if mk.is_empty() { strip_maker(raw.clone(), &cm) } else { md.clone() };
+            maker_matches(&cm, &mk) && strip_maker(tokens(&c.model), &cm) == md
+        })
     }
 
     /// The best profile for an EXIF lens model (and lens make, when the file has one), preferring lenses for the
     /// camera's mount. `None` when no name is a convincing match.
     pub fn find_lens(&self, lens_make: Option<&str>, lens_model: &str, camera: Option<&Camera>) -> Option<&Profile> {
-        self.best_lens(lens_make, lens_model, camera, |_| true)
+        self.best_lens(lens_make, lens_model, camera, |_| true).or_else(|| self.find_fixed_lens(&tokens(lens_model), camera?))
     }
 
     /// [`find_lens`](Self::find_lens) among the profiles one of whose names is exactly `name`
@@ -204,6 +211,64 @@ impl LensDb {
         }
         best.map(|(_, l)| l)
     }
+
+    /// Compact cameras with a built-in lens: lensfun names the lens after the camera and gives it the camera's own
+    /// mount, while EXIF reports only a focal / aperture range ("6.0-72.0 mm f/2.7-3.5"). Match by mount, then by
+    /// how much of the calibrated focal range lies inside the EXIF focal range.
+    fn find_fixed_lens(&self, exif: &[String], cam: &Camera) -> Option<&Profile> {
+        if cam.mount.is_empty() {
+            return None;
+        }
+        let cands: Vec<&Profile> = self
+            .lenses
+            .iter()
+            .filter(|l| l.mounts.iter().any(|m| m.eq_ignore_ascii_case(&cam.mount)))
+            .filter(|l| !l.models.iter().any(|m| tokens(m).iter().any(|t| t == "mm")))
+            .collect();
+        // an interchangeable mount has many lenses; a built-in lens has one or a few profiles
+        if cands.is_empty() || cands.len() > MAX_FIXED_CANDIDATES {
+            return None;
+        }
+        let Some((lo, hi)) = exif_focal_range(exif) else {
+            return (cands.len() == 1).then(|| cands[0]);
+        };
+        let (lo, hi) = (lo * 0.9, hi * 1.1);
+        if !lo.is_finite() || !hi.is_finite() {
+            return None;
+        }
+        let mut best: Option<(f64, &Profile)> = None;
+        for p in cands {
+            let focals: Vec<f64> =
+                p.distortion.iter().map(|d| d.0).chain(p.tca.iter().map(|t| t.0)).chain(p.vignetting.iter().map(|v| v.focal)).collect();
+            if focals.is_empty() {
+                continue;
+            }
+            let score = focals.iter().filter(|f| (lo..=hi).contains(*f)).count() as f64 / focals.len() as f64;
+            if score > 0.0 && best.is_none_or(|(s, _)| score > s) {
+                best = Some((score, p));
+            }
+        }
+        best.map(|(_, p)| p)
+    }
+}
+
+/// Most profiles a camera's mount may have for it to count as a built-in-lens mount.
+const MAX_FIXED_CANDIDATES: usize = 4;
+
+/// Drop the maker's words from a model name ("Canon PowerShot S5 IS" → "powershot s5 is").
+fn strip_maker(model: Vec<String>, maker: &[String]) -> Vec<String> {
+    let first = maker.first();
+    let stripped: Vec<String> = model.iter().filter(|t| Some(*t) != first).cloned().collect();
+    if stripped.is_empty() { model } else { stripped }
+}
+
+/// The numbers before the first "mm" in tokenised EXIF lens text: (shortest, longest) focal length.
+fn exif_focal_range(exif: &[String]) -> Option<(f64, f64)> {
+    let end = exif.iter().position(|t| t == "mm")?;
+    let nums: Vec<f64> = exif.get(..end)?.iter().filter_map(|t| t.parse::<f64>().ok()).filter(|v| v.is_finite() && *v > 0.0).collect();
+    let lo = nums.iter().copied().fold(f64::INFINITY, f64::min);
+    let hi = nums.iter().copied().fold(0.0, f64::max);
+    (lo.is_finite() && hi > 0.0).then_some((lo, hi))
 }
 
 fn read_capped(path: &Path) -> Result<String, Error> {
diff --git a/crates/lensdb/src/tests.rs b/crates/lensdb/src/tests.rs
--- a/crates/lensdb/src/tests.rs
+++ b/crates/lensdb/src/tests.rs
@@ -287,3 +287,82 @@ fn a_named_profile_is_found_behind_a_tie() {
     assert_eq!(db.find_lens_named(None, &name, None).unwrap().maker, "Testcam");
     assert!(db.find_lens_named(None, "70-200mm f/4", None).is_none());
 }
+
+#[test]
+fn built_in_lens_matches_by_camera_mount_and_focal_range() {
+    let xml = r#"<lensdatabase>
+  <camera><maker>Canon</maker><model>Canon PowerShot S5 IS</model><model lang="en">PowerShot S5 IS</model><mount>canonS5</mount><cropfactor>6.0</cropfactor></camera>
+  <lens><maker>Canon</maker><model>Canon PowerShot S5 IS &amp; compatibles</model><mount>canonS5</mount><cropfactor>6.0</cropfactor>
+    <calibration><distortion model="poly3" focal="6" k1="0.01" /><distortion model="poly3" focal="72" k1="0.0" /></calibration></lens>
+  <lens><maker>Canon</maker><model>Canon EF 50mm f/1.8</model><mount>Canon EF</mount>
+    <calibration><distortion model="poly3" focal="50" k1="0.01" /></calibration></lens>
+</lensdatabase>"#;
+    let db = LensDb::parse(xml).unwrap();
+    let cam = db.find_camera("Canon", "Canon PowerShot S5 IS");
+    assert!(cam.is_some());
+    assert!(db.find_camera("Canon", "PowerShot S5 IS").is_some());
+    let l = db.find_lens(None, "6.0-72.0 mm f/2.7-3.5", cam).unwrap();
+    assert_eq!(l.mounts, ["canonS5"]);
+    assert!(db.find_lens(None, "6.0-72.0 mm f/2.7-3.5", None).is_none());
+    // a file with no make still finds the camera by its full model name
+    assert!(db.find_camera("", "Canon PowerShot S5 IS").is_some());
+    // the exact-name lookup never falls back to the camera's mount
+    assert!(db.find_lens_named(None, "6.0-72.0 mm f/2.7-3.5", cam).is_none());
+    // a focal length that overflows to infinity is ignored, not taken as an unbounded range
+    let huge = format!("6.0-{}9 mm f/2.7-3.5", "9".repeat(400));
+    assert_eq!(db.find_lens(None, &huge, cam).unwrap().mounts, ["canonS5"]);
+    assert_eq!(exif_focal_range(&tokens(&huge)), Some((6.0, 6.0)));
+}
+
+/// A hand-edited or corrupt database may carry any number: infinities, NaN, enormous or negative
+/// coefficients, absurd crop factors and aspect ratios. Whatever loads must give corrections that
+/// are `None` or entirely finite, for any shot including nonsense ones; names of any shape find
+/// nothing rather than panic.
+#[test]
+fn hostile_numbers_give_finite_corrections_or_none() {
+    let values = ["inf", "-inf", "NaN", "1e300", "-1e300", "1e-300", "0", "-1", "1e6", "99999"];
+    let mut cases = 0;
+    for v in values {
+        for xml in [
+            format!(
+                r#"<lens><maker>M</maker><model>M 1</model><cropfactor>{v}</cropfactor><aspect-ratio>{v}</aspect-ratio><calibration><distortion model="poly3" focal="24" k1="0.01"/><vignetting model="pa" focal="24" aperture="2.8" k1="-0.3"/></calibration></lens>"#
+            ),
+            format!(
+                r#"<lens><maker>M</maker><model>M 1</model><calibration cropfactor="{v}" aspect-ratio="{v}"><distortion model="ptlens" focal="24" a="{v}" b="{v}" c="{v}"/><tca model="poly3" focal="24" vr="{v}" vb="{v}" cr="{v}" cb="{v}" br="{v}" bb="{v}"/></calibration></lens>"#
+            ),
+            format!(
+                r#"<lens><maker>M</maker><model>M 1</model><calibration><distortion model="poly5" focal="{v}" k1="{v}" k2="{v}"/><distortion model="poly3" focal="50" k1="{v}"/><vignetting model="pa" focal="{v}" aperture="{v}" distance="{v}" k1="{v}" k2="{v}" k3="{v}"/><tca model="linear" focal="{v}" kr="{v}" kb="{v}"/></calibration></lens>"#
+            ),
+        ] {
+            let Ok(db) = LensDb::parse(&format!("<lensdatabase>{xml}</lensdatabase>")) else { continue };
+            for p in &db.lenses {
+                for focal in [f64::NAN, f64::INFINITY, -1.0, 0.0, 1e-9, 8.0, 24.0, 50.0, 1e9] {
+                    for (w, h) in [(0.0, 0.0), (1.0, 1.0), (2.0, 2.0), (6000.0, 4000.0), (1e9, 1.0), (f64::NAN, 10.0), (f64::INFINITY, 4000.0)] {
+                        for aperture in [None, Some(f64::NAN), Some(-2.0), Some(0.0), Some(2.8), Some(1e9)] {
+                            for crop in [None, Some(0.0), Some(f64::NAN), Some(-1.0), Some(1.5), Some(1e9)] {
+                                let shot = Shot { focal, aperture, distance: Some(f64::NAN), crop_factor: crop, width: w, height: h };
+                                cases += 1;
+                                let Some(c) = p.correction(&shot) else { continue };
+                                let planes =
+                                    c.warp.iter().flat_map(|wp| wp.planes.iter().flatten().copied().chain([wp.radius, wp.center.x, wp.center.y]));
+                                let vig = c.vignette.iter().flat_map(|vg| vg.k.iter().copied().chain([vg.radius, vg.center.x, vg.center.y]));
+                                assert!(planes.chain(vig).all(f64::is_finite), "{v}: focal {focal} {w}x{h} {aperture:?} {crop:?}");
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    assert!(cases > 1000);
+
+    let db = db();
+    let long = "x".repeat(100_000);
+    for s in ["", " ", "\t\n", "\0", "ß İ ǅ", "....", "1.2.3.4mm", "-mm", long.as_str()] {
+        let _ = db.find_camera(s, s);
+        let _ = db.find_lens(Some(s), s, db.cameras.first());
+        let _ = db.find_lens(None, s, None);
+    }
+    assert!(db.find_camera("", "").is_none());
+    assert!(db.find_lens(None, "  ", None).is_none());
+}
