From 37adf3fdf2ab4d252e06744bbdd580eaa388338f Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Tue, 29 Oct 2024 18:22:17 +0000 Subject: [PATCH] OSS-Fuzz: OSS-Fuzz fuzzing integration Signed-off-by: Arthur Chan --- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 70 +++++ fuzz/fuzz_targets/fuzz-aat.rs | 109 ++++++++ fuzz/fuzz_targets/fuzz-base.rs | 162 +++++++++++ fuzz/fuzz_targets/fuzz-cpal.rs | 114 ++++++++ fuzz/fuzz_targets/fuzz-glyph-index.rs | 38 +++ fuzz/fuzz_targets/fuzz-outline.rs | 58 ++++ fuzz/fuzz_targets/fuzz-table-with-builder.rs | 70 +++++ fuzz/fuzz_targets/fuzz-table.rs | 271 +++++++++++++++++++ fuzz/fuzz_targets/fuzz-variable-outline.rs | 60 ++++ 10 files changed, 956 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/fuzz-aat.rs create mode 100644 fuzz/fuzz_targets/fuzz-base.rs create mode 100644 fuzz/fuzz_targets/fuzz-cpal.rs create mode 100644 fuzz/fuzz_targets/fuzz-glyph-index.rs create mode 100644 fuzz/fuzz_targets/fuzz-outline.rs create mode 100644 fuzz/fuzz_targets/fuzz-table-with-builder.rs create mode 100644 fuzz/fuzz_targets/fuzz-table.rs create mode 100644 fuzz/fuzz_targets/fuzz-variable-outline.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..603af1b --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "ttf-parser-fuzz" +version = "0.0.0" +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +serde = "1.0" +serde_json = "1.0" +ttf-parser = { path = "..", features = ["apple-layout"] } + +[[bin]] +name = "fuzz-base" +path = "fuzz_targets/fuzz-base.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-aat" +path = "fuzz_targets/fuzz-aat.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-cpal" +path = "fuzz_targets/fuzz-cpal.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-table" +path = "fuzz_targets/fuzz-table.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-table-with-builder" +path = "fuzz_targets/fuzz-table-with-builder.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-glyph-index" +path = "fuzz_targets/fuzz-glyph-index.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-outline" +path = "fuzz_targets/fuzz-outline.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz-variable-outline" +path = "fuzz_targets/fuzz-variable-outline.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/fuzz-aat.rs b/fuzz/fuzz_targets/fuzz-aat.rs new file mode 100644 index 0000000..dd074f4 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-aat.rs @@ -0,0 +1,109 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::num::NonZeroU16; +use ttf_parser::{GlyphId, apple_layout::Lookup}; + +fn u16_to_u8_vec(data: &[u16]) -> Vec { + let mut u8_data = Vec::with_capacity(data.len() * 2); + for &value in data { + u8_data.push((value >> 8) as u8); + u8_data.push(value as u8); + } + u8_data +} + +fuzz_target!(|data: &[u8]| { + // Skip this iteration if data not enough + if data.len() < 4 { + return; + } + + let (format_data, rest) = data.split_at(2); + let format = u16::from_be_bytes([format_data[0], format_data[1]]); + + let random_u16 = |data: &[u8], idx: usize| -> Option { + if data.len() > idx + 1 { + Some(u16::from_be_bytes([data[idx], data[idx + 1]])) + } else { + None + } + }; + + let lookup_len = NonZeroU16::new(1).unwrap(); + + // Use valid fromat 0 2 4 6 8 10 for fuzzing chioce + match format { + 0 => { + if let Some(value) = random_u16(rest, 0) { + let lookup_data = u16_to_u8_vec(&[0, value]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(0)); + let _ = table.value(GlyphId(1)); + } + } + } + 2 => { + if let Some(segment_size) = random_u16(rest, 2) { + let lookup_data = u16_to_u8_vec(&[2, segment_size, 1]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(118)); + let _ = table.value(GlyphId(5)); + } + } + } + 4 => { + if let Some(segment_size) = random_u16(rest, 2) { + let lookup_data = u16_to_u8_vec(&[4, segment_size, 1]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(118)); + let _ = table.value(GlyphId(7)); + } + } + } + 6 => { + if let Some(segment_size) = random_u16(rest, 2) { + let lookup_data = u16_to_u8_vec(&[6, segment_size]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(0)); + let _ = table.value(GlyphId(10)); + } + } + } + 8 => { + if let Some(glyph_count) = random_u16(rest, 2) { + let lookup_data = u16_to_u8_vec(&[8, 0, glyph_count]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(0)); + let _ = table.value(GlyphId(5)); + } + } + } + 10 => { + if let Some(value_size) = random_u16(rest, 2) { + let lookup_data = u16_to_u8_vec(&[10, value_size, 0]); + if let Some(table) = Lookup::parse(lookup_len, &lookup_data) { + let _ = table.value(GlyphId(0)); + let _ = table.value(GlyphId(1)); + } + } + } + _ => { + // Ignore invliad format of 1 3 5 7 9 + } + } +}); diff --git a/fuzz/fuzz_targets/fuzz-base.rs b/fuzz/fuzz_targets/fuzz-base.rs new file mode 100644 index 0000000..c8db367 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-base.rs @@ -0,0 +1,162 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use ttf_parser::{Face, GlyphId, name_id}; + +fn get_fuzzed_char(data: &[u8]) -> char { + if data.len() >= 4 { + let code_point = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + std::char::from_u32(code_point).unwrap_or('A') + } else { + 'A' + } +} + +fuzz_target!(|data: &[u8]| { + // Skip this iteration if no data is provided + if data.is_empty() { + return; + } + + let choice = data[0] % 15; + let fuzz_data = &data[1..]; + + let face_result = Face::parse(fuzz_data, 0); + let id = if !data.is_empty() { data[0] as u16 } else { 0 }; + let glyph_id = GlyphId(id); + let random_char = get_fuzzed_char(fuzz_data); + + // Randomly fuzz functions from base ttf-parser + match choice { + 0 => { + if let Ok(face) = face_result { + let mut family_names = Vec::new(); + for name in face.names() { + if name.name_id == name_id::FULL_NAME && name.is_unicode() { + if let Some(family_name) = name.to_string() { + let language = name.language(); + family_names.push(format!( + "{} ({}, {})", + family_name, + language.primary_language(), + language.region() + )); + } + } + } + let _ = family_names; + } + }, + 1 => { + if let Ok(face) = face_result { + let _ = face.units_per_em(); + let _ = face.ascender(); + let _ = face.descender(); + let _ = face.line_gap(); + let _ = face.global_bounding_box(); + } + }, + 2 => { + if let Ok(face) = face_result { + let _ = face.is_regular(); + let _ = face.is_bold(); + let _ = face.is_italic(); + let _ = face.is_oblique(); + let _ = face.is_variable(); + } + }, + 3 => { + if let Ok(face) = face_result { + let _ = face.number_of_glyphs(); + let _ = face.glyph_bounding_box(glyph_id); + let _ = face.glyph_hor_advance(glyph_id); + let _ = face.glyph_index(random_char); + } + }, + 4 => { + if let Ok(face) = face_result { + let _ = face.underline_metrics(); + let _ = face.strikeout_metrics(); + let _ = face.subscript_metrics(); + let _ = face.superscript_metrics(); + } + }, + 5 => { + if let Ok(face) = face_result { + let post_script_name = face.names().into_iter() + .find(|name| name.name_id == name_id::POST_SCRIPT_NAME && name.is_unicode()) + .and_then(|name| name.to_string()); + let _ = post_script_name; + } + }, + 6 => { + if let Ok(face) = face_result { + let _ = face.glyph_raster_image(glyph_id, u16::MAX); + } + }, + 7 => { + if let Ok(face) = face_result { + if let Some(stat) = face.tables().stat { + for axis in stat.axes { + let _ = axis.tag; + } + } + } + }, + 8 => { + if let Ok(face) = face_result { + if let Some(svg_table) = face.tables().svg { + let _ = svg_table.documents.find(glyph_id); + } + } + }, + 9 => { + if let Ok(face) = face_result { + let _ = face.permissions(); + let _ = face.is_variable(); + } + }, + 10 => { + if let Ok(face) = face_result { + let _ = face.glyph_hor_side_bearing(glyph_id); + let _ = face.glyph_ver_advance(glyph_id); + let _ = face.glyph_ver_side_bearing(glyph_id); + } + }, + 11 => { + if let Ok(face) = face_result { + let _ = face.tables().os2; + } + }, + 12 => { + if let Ok(face) = face_result { + let _ = face.tables().head; + } + }, + 13 => { + if let Ok(face) = face_result { + let _ = face.tables().maxp; + } + }, + 14 => { + if let Ok(face) = face_result { + let _ = face.tables().hhea; + } + }, + _ => return, + } +}); diff --git a/fuzz/fuzz_targets/fuzz-cpal.rs b/fuzz/fuzz_targets/fuzz-cpal.rs new file mode 100644 index 0000000..1b40b74 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-cpal.rs @@ -0,0 +1,114 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use ttf_parser::{colr, GlyphId, cpal, RgbaColor}; + +fuzz_target!(|data: &[u8]| { + if data.len() < 10 { + return; + } + + // Fuzz CPAL/COLR parse + if let Some(cpal_table) = cpal::Table::parse(data) { + if let Some(colr_table) = colr::Table::parse(cpal_table, data) { + let mut painter = VecPainter(vec![]); + let glyph_id = GlyphId(data[0] as u16); + let _ = colr_table.paint(glyph_id, 0, &mut painter, &[], RgbaColor::new(0, 0, 0, 255)); + } + } +}); + +// Custom VecPainter +struct VecPainter(Vec); + +impl<'a> colr::Painter<'a> for VecPainter { + fn outline_glyph(&mut self, glyph_id: GlyphId) { + self.0.push(Command::OutlineGlyph(glyph_id)); + } + + fn paint(&mut self, paint: colr::Paint<'a>) { + let custom_paint = match paint { + colr::Paint::Solid(color) => CustomPaint::Solid(color), + colr::Paint::LinearGradient(lg) => CustomPaint::LinearGradient( + lg.x0, lg.y0, lg.x1, lg.y1, lg.x2, lg.y2, lg.extend, + lg.stops(0, &[]).map(|stop| CustomStop(stop.stop_offset, stop.color)).collect(), + ), + colr::Paint::RadialGradient(rg) => CustomPaint::RadialGradient( + rg.x0, rg.y0, rg.r0, rg.r1, rg.x1, rg.y1, rg.extend, + rg.stops(0, &[]).map(|stop| CustomStop(stop.stop_offset, stop.color)).collect(), + ), + colr::Paint::SweepGradient(sg) => CustomPaint::SweepGradient( + sg.center_x, sg.center_y, sg.start_angle, sg.end_angle, sg.extend, + sg.stops(0, &[]).map(|stop| CustomStop(stop.stop_offset, stop.color)).collect(), + ), + }; + + self.0.push(Command::Paint(custom_paint)); + } + + fn push_layer(&mut self, mode: colr::CompositeMode) { + self.0.push(Command::PushLayer(mode)); + } + + fn pop_layer(&mut self) { + self.0.push(Command::PopLayer); + } + + fn push_transform(&mut self, transform: ttf_parser::Transform) { + self.0.push(Command::Transform(transform)); + } + + fn pop_transform(&mut self) { + self.0.push(Command::PopTransform); + } + + fn push_clip(&mut self) { + self.0.push(Command::PushClip); + } + + fn push_clip_box(&mut self, clipbox: colr::ClipBox) { + self.0.push(Command::PushClipBox(clipbox)); + } + + fn pop_clip(&mut self) { + self.0.push(Command::PopClip); + } +} + +#[derive(Clone, Debug, PartialEq)] +struct CustomStop(f32, RgbaColor); + +#[derive(Clone, Debug, PartialEq)] +enum CustomPaint { + Solid(RgbaColor), + LinearGradient(f32, f32, f32, f32, f32, f32, colr::GradientExtend, Vec), + RadialGradient(f32, f32, f32, f32, f32, f32, colr::GradientExtend, Vec), + SweepGradient(f32, f32, f32, f32, colr::GradientExtend, Vec), +} + +#[derive(Clone, Debug, PartialEq)] +enum Command { + OutlineGlyph(GlyphId), + Paint(CustomPaint), + PushLayer(colr::CompositeMode), + PopLayer, + Transform(ttf_parser::Transform), + PopTransform, + PushClip, + PushClipBox(colr::ClipBox), + PopClip, +} diff --git a/fuzz/fuzz_targets/fuzz-glyph-index.rs b/fuzz/fuzz_targets/fuzz-glyph-index.rs new file mode 100644 index 0000000..ef299c6 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-glyph-index.rs @@ -0,0 +1,38 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Transformed from ttf-parser/testing-tools/ttf-fuzz/src/fuzz-glyph-index.rs +// for LibFuzzer and Cargo-Fuzz compatibility + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use ttf_parser::Face; + +const CHARS: &[char] = &[ + '\u{0}', + 'A', + 'ะค', + '0', + '\u{D7FF}', + '\u{10FFFF}', +]; + +fuzz_target!(|data: &[u8]| { + if let Ok(face) = Face::parse(data, 0) { + for c in CHARS { + let _ = face.glyph_index(*c); + } + } +}); diff --git a/fuzz/fuzz_targets/fuzz-outline.rs b/fuzz/fuzz_targets/fuzz-outline.rs new file mode 100644 index 0000000..3d18ef5 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-outline.rs @@ -0,0 +1,58 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Transformed from ttf-parser/testing-tools/ttf-fuzz/src/fuzz-outline.rs +// for LibFuzzer and Cargo-Fuzz compatibility + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use ttf_parser::{Face, GlyphId, OutlineBuilder}; + +struct Builder(usize); + +impl OutlineBuilder for Builder { + #[inline] + fn move_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + + #[inline] + fn line_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + + #[inline] + fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { + self.0 += 2; + } + + #[inline] + fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { + self.0 += 3; + } + + #[inline] + fn close(&mut self) { + self.0 += 1; + } +} + +fuzz_target!(|data: &[u8]| { + if let Ok(face) = Face::parse(data, 0) { + for id in 0..face.number_of_glyphs() { + let _ = face.outline_glyph(GlyphId(id), &mut Builder(0)); + } + } +}); diff --git a/fuzz/fuzz_targets/fuzz-table-with-builder.rs b/fuzz/fuzz_targets/fuzz-table-with-builder.rs new file mode 100644 index 0000000..8143c5a --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-table-with-builder.rs @@ -0,0 +1,70 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use ttf_parser::{GlyphId, Face, cff}; +use std::fmt::Write; + +fuzz_target!(|fuzz_data: &[u8]| { + // Skip this iteration if data is not enough + if fuzz_data.is_empty() { + return; + } + + let choice = fuzz_data[0] % 2; + let data = &fuzz_data[1..]; + + // Randomly choose a module to fuzz + if (choice) == 0 { + // Fuzzing CFF1 module + if let Some(table) = cff::Table::parse(data) { + let mut builder = Builder(String::new()); + let _ = table.outline(GlyphId(0), &mut builder); + } + } else { + // Fuzzing glyf module + if let Ok(face) = Face::parse(data, 0) { + let mut builder = Builder(String::new()); + let _ = face.outline_glyph(GlyphId(0), &mut builder); + } + } +}); + +// Custom Builder implementation +struct Builder(String); + +impl ttf_parser::OutlineBuilder for Builder { + fn move_to(&mut self, x: f32, y: f32) { + let _ = write!(&mut self.0, "M {} {} ", x, y); + } + + fn line_to(&mut self, x: f32, y: f32) { + let _ = write!(&mut self.0, "L {} {} ", x, y); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + let _ = write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + let _ = write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y); + } + + fn close(&mut self) { + let _ = write!(&mut self.0, "Z "); + } +} diff --git a/fuzz/fuzz_targets/fuzz-table.rs b/fuzz/fuzz_targets/fuzz-table.rs new file mode 100644 index 0000000..ffd046c --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-table.rs @@ -0,0 +1,271 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::num::NonZeroU16; +use ttf_parser::{GlyphId}; +use ttf_parser::{ankr, avar, cmap, feat, fvar, gdef, gvar, hmtx, hvar, kern, kerx}; +use ttf_parser::{loca, math, maxp, morx, mvar, name, sbix, stat, svg, trak, vhea, vorg, vvar}; + +fuzz_target!(|fuzz_data: &[u8]| { + // Skip this iteration if data is empty + if fuzz_data.is_empty() { + return; + } + + // Randomly choose a fuzzing target function + let choice = fuzz_data[0] % 26; + let data = &fuzz_data[1..]; + match choice { + 0 => { + // Fuzz cmap module + if let Some(subtable0) = cmap::Subtable0::parse(data) { + subtable0.glyph_index(0x41); + subtable0.glyph_index(0x42); + let mut codepoints = vec![]; + subtable0.codepoints(|c| codepoints.push(c)); + } + if let Some(subtable4) = cmap::Subtable4::parse(data) { + subtable4.glyph_index(0x41); + subtable4.glyph_index(0x42); + let mut codepoints = vec![]; + subtable4.codepoints(|c| codepoints.push(c)); + } + } + 1 => { + // Fuzz ankr module + if let Some(table) = ankr::Table::parse(NonZeroU16::new(1).unwrap(), data) { + if let Some(points) = table.points(GlyphId(0)) { + for point in points { + let _ = point.x; + let _ = point.y; + } + } + } + } + 2 => { + // Fuzz feat module + if let Some(feat_table) = feat::Table::parse(data) { + for feature in feat_table.names { + let _ = feature.exclusive; + let _ = feature.default_setting_index; + } + } + } + 3 => { + // Fuzz hmtx module + if let Some(hmtx_table) = hmtx::Table::parse(1, NonZeroU16::new(1).unwrap(), data) { + let _ = hmtx_table.number_of_metrics; + } + } + 4 => { + // Fuzz maxp module + if let Some(maxp_table) = maxp::Table::parse(data) { + let _ = maxp_table.number_of_glyphs; + } + } + 5 => { + // Fuzz sbix module + if let Some(table) = sbix::Table::parse(NonZeroU16::new(1).unwrap(), data) { + for strike in table.strikes { + for i in 0..strike.len() { + if let Some(glyph_data) = strike.get(GlyphId(i as u16)) { + let _ = glyph_data.x; + let _ = glyph_data.y; + let _ = glyph_data.width; + let _ = glyph_data.height; + let _ = glyph_data.pixels_per_em; + let _ = glyph_data.format; + } + } + } + } + } + 6 => { + // Fuzz trak module + if let Some(trak_table) = trak::Table::parse(data) { + for track in trak_table.horizontal.tracks { + let _ = track.value; + for value in track.values { + let _ = value; + } + } + } + } + 7 => { + // Fuzz kern module + if let Some(kern_table) = kern::Table::parse(data) { + for subtable in kern_table.subtables.into_iter() { + if let Some(kern_val) = subtable.glyphs_kerning(GlyphId(1), GlyphId(2)) { + let _ = kern_val; + } + } + } + } + 8 => { + // Fuzz kerx module + if let Some(kerx_table) = kerx::Table::parse(NonZeroU16::new(1).unwrap(), data) { + for subtable in kerx_table.subtables.into_iter() { + if let Some(kerx_val) = subtable.glyphs_kerning(GlyphId(1), GlyphId(2)) { + let _ = kerx_val; + } + } + } + } + 9 => { + // Fuzz loca module + if let Some(loca_table) = loca::Table::parse(NonZeroU16::new(1).unwrap(), ttf_parser::head::IndexToLocationFormat::Short, data) { + if let Some(range) = loca_table.glyph_range(GlyphId(1)) { + let _ = range.start; + let _ = range.end; + } + } + } + 10 => { + // Fuzz math constants module + if let Some(math_table) = math::Table::parse(data) { + if let Some(constants) = math_table.constants { + let _ = constants.axis_height(); + let _ = constants.script_percent_scale_down(); + } + } + } + 11 => { + // Fuzz math kern info module + if let Some(math_table) = math::Table::parse(data) { + if let Some(glyph_info) = math_table.glyph_info { + if let Some(kern_infos) = glyph_info.kern_infos { + if let Some(kern_info) = kern_infos.get(GlyphId(1)) { + let _ = kern_info.top_right; + let _ = kern_info.bottom_left; + } + } + } + } + } + 12 => { + // Fuzz gvar module + let _ = gvar::Table::parse(data); + } + 13 => { + // Fuzz hvar module + let _ = hvar::Table::parse(data); + } + 14 => { + // Fuzz avar module + let _ = avar::Table::parse(data); + } + 15 => { + // Fuzz fvar module + if let Some(fvar_table) = fvar::Table::parse(data) { + for axis in fvar_table.axes { + let _ = axis.tag; + } + } + } + 16 => { + // Fuzz gdef module + if let Some(gdef_table) = gdef::Table::parse(data) { + let _ = gdef_table.glyph_class(GlyphId(1)); + } + } + 17 => { + // Fuzz morx module + if let Some(morx_table) = morx::Table::parse(NonZeroU16::new(1).unwrap(), data) { + for chain in morx_table.chains { + let _ = chain.default_flags; + for feature in chain.features { + let _ = feature.kind; + } + } + } + } + 18 => { + // Fuzz mvar module + if let Some(mvar_table) = mvar::Table::parse(data) { + let _ = mvar_table.metric_offset(ttf_parser::Tag::from_bytes(b"wdth"), &[]); + } + } + 19 => { + // Fuzz name module + if let Some(name_table) = name::Table::parse(data) { + for index in 0..name_table.names.len() { + if let Some(name) = name_table.names.get(index) { + let _ = name.to_string(); + } + } + } + } + 20 => { + // Fuzz stat module + if let Some(stat_table) = stat::Table::parse(data) { + for subtable in stat_table.subtables() { + let _ = subtable.name_id(); + } + } + } + 21 => { + // Fuzz svg module + if let Some(svg_table) = svg::Table::parse(data) { + for index in 0..svg_table.documents.len() { + if let Some(svg_doc) = svg_table.documents.get(index) { + let _ = svg_doc.glyphs_range(); + } + } + } + } + 22 => { + // Fuzz trak module + if let Some(trak_table) = trak::Table::parse(data) { + for track in trak_table.horizontal.tracks { + let _ = track.value; + for value in track.values { + let _ = value; + } + } + } + } + 23 => { + // Fuzz vhea module + if let Some(vhea_table) = vhea::Table::parse(data) { + let _ = vhea_table.ascender; + let _ = vhea_table.descender; + let _ = vhea_table.line_gap; + let _ = vhea_table.number_of_metrics; + } + } + 24 => { + // Fuzz vorg module + if let Some(vorg_table) = vorg::Table::parse(data) { + let _ = vorg_table.default_y; + for metrics in vorg_table.metrics { + let _ = metrics.glyph_id; + let _ = metrics.y; + } + } + } + 25 => { + // Fuzz vvar module + if let Some(vvar_table) = vvar::Table::parse(data) { + let _ = vvar_table.advance_offset(GlyphId(1), &[]); + let _ = vvar_table.top_side_bearing_offset(GlyphId(1), &[]); + let _ = vvar_table.bottom_side_bearing_offset(GlyphId(1), &[]); + let _ = vvar_table.vertical_origin_offset(GlyphId(1), &[]); + } + } + _ => {} + } +}); diff --git a/fuzz/fuzz_targets/fuzz-variable-outline.rs b/fuzz/fuzz_targets/fuzz-variable-outline.rs new file mode 100644 index 0000000..4eb87f8 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz-variable-outline.rs @@ -0,0 +1,60 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Transformed from ttf-parser/testing-tools/ttf-fuzz/src/fuzz-variable-outline.rs +// for LibFuzzer and Cargo-Fuzz compatibility + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use ttf_parser::{Face, GlyphId, OutlineBuilder, Tag}; + +struct Builder(usize); + +impl OutlineBuilder for Builder { + #[inline] + fn move_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + + #[inline] + fn line_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + + #[inline] + fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { + self.0 += 2; + } + + #[inline] + fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { + self.0 += 3; + } + + #[inline] + fn close(&mut self) { + self.0 += 1; + } +} + +fuzz_target!(|data: &[u8]| { + if let Ok(mut face) = Face::parse(data, 0) { + if face.set_variation(Tag::from_bytes(b"wght"), 500.0).is_some() { + for id in 0..face.number_of_glyphs() { + let _ = face.outline_glyph(GlyphId(id), &mut Builder(0)); + } + } + } +});