blob: 4da0c2bf255c99104be3d36e4576a32e788494fe [file] [log] [blame]
// ftfuzzer.cc
//
// A fuzzing function to test FreeType with libFuzzer.
//
// Copyright 2015-2017 by
// David Turner, Robert Wilhelm, and Werner Lemberg.
//
// This file is part of the FreeType project, and may only be used,
// modified, and distributed under the terms of the FreeType project
// license, LICENSE.TXT. By continuing to use, modify, or distribute
// this file you indicate that you have read the license and
// understand and accept it fully.
// we use `unique_ptr', `decltype', and other gimmicks defined since C++11
#if __cplusplus < 201103L
# error "a C++11 compiler is needed"
#endif
#include <archive.h>
#include <archive_entry.h>
#include <assert.h>
#include <stdint.h>
#include <memory>
#include <vector>
using namespace std;
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_CACHE_H
#include FT_CACHE_CHARMAP_H
#include FT_CACHE_IMAGE_H
#include FT_CACHE_SMALL_BITMAPS_H
#include FT_SYNTHESIS_H
#include FT_ADVANCES_H
#include FT_OUTLINE_H
#include FT_BBOX_H
#include FT_MODULE_H
#include FT_CFF_DRIVER_H
#include FT_TRUETYPE_DRIVER_H
#include FT_MULTIPLE_MASTERS_H
static FT_Library library;
static int InitResult;
struct FT_Global
{
FT_Global()
{
InitResult = FT_Init_FreeType( &library );
if ( InitResult )
return;
// try to activate Adobe's CFF engine; it might not be the default
unsigned int cff_hinting_engine = FT_CFF_HINTING_ADOBE;
FT_Property_Set( library,
"cff",
"hinting-engine", &cff_hinting_engine );
}
~FT_Global()
{
FT_Done_FreeType( library );
}
};
FT_Global global_ft;
// We want to select n values at random (without repetition),
// with 0 < n <= N. The algorithm is taken from TAoCP, Vol. 2
// (Algorithm S, selection sampling technique)
struct Random
{
int n;
int N;
int t; // total number of values so far
int m; // number of selected values so far
uint32_t r; // the current pseudo-random number
Random( int n_,
int N_ )
: n( n_ ),
N( N_ )
{
t = 0;
m = 0;
// Ideally, this should depend on the input file,
// for example, taking the sha256 as input;
// however, this is overkill for fuzzying tests.
r = 12345;
}
int get()
{
if ( m >= n )
return -1;
Redo:
// We can't use `rand': different C libraries might provide
// different implementations of this function. As a replacement,
// we use a 32bit version of the `xorshift' algorithm.
r ^= r << 13;
r ^= r >> 17;
r ^= r << 5;
double U = double( r ) / UINT32_MAX;
if ( ( N - t ) * U >= ( n - m ) )
{
t++;
goto Redo;
}
t++;
m++;
return t;
}
};
static int
archive_read_entry_data( struct archive *ar,
vector<FT_Byte> *vw )
{
int r;
const FT_Byte* buff;
size_t size;
int64_t offset;
for (;;)
{
r = archive_read_data_block( ar,
reinterpret_cast<const void**>( &buff ),
&size,
&offset );
if ( r == ARCHIVE_EOF )
return ARCHIVE_OK;
if ( r != ARCHIVE_OK )
return r;
vw->insert( vw->end(), buff, buff + size );
}
}
static vector<vector<FT_Byte>>
parse_data( const uint8_t* data,
size_t size )
{
struct archive_entry* entry;
int r;
vector<vector<FT_Byte>> files;
unique_ptr<struct archive,
decltype ( archive_read_free )*> a( archive_read_new(),
archive_read_free );
// activate reading of uncompressed tar archives
archive_read_support_format_tar( a.get() );
// the need for `const_cast' was removed with libarchive commit be4d4dd
if ( !( r = archive_read_open_memory(
a.get(),
const_cast<void*>(static_cast<const void*>( data ) ),
size ) ) )
{
unique_ptr<struct archive,
decltype ( archive_read_close )*> a_open( a.get(),
archive_read_close );
// read files contained in archive
for (;;)
{
r = archive_read_next_header( a_open.get(), &entry );
if ( r == ARCHIVE_EOF )
break;
if ( r != ARCHIVE_OK )
break;
vector<FT_Byte> entry_data;
r = archive_read_entry_data( a.get(), &entry_data );
if ( r != ARCHIVE_OK )
break;
files.push_back( move( entry_data ) );
}
}
if ( files.size() == 0 )
files.emplace_back( data, data + size );
return files;
}
static void
setIntermediateAxis( FT_Face face )
{
// only handle Multiple Masters and GX variation fonts
if ( !FT_HAS_MULTIPLE_MASTERS( face ) )
return;
// get variation data for current instance
FT_MM_Var* variations_ptr = nullptr;
if ( FT_Get_MM_Var( face, &variations_ptr ) )
return;
unique_ptr<FT_MM_Var,
decltype ( free )*> variations( variations_ptr, free );
vector<FT_Fixed> coords( variations->num_axis );
// select an arbitrary instance
for ( unsigned int i = 0; i < variations->num_axis; i++ )
coords[i] = ( variations->axis[i].minimum +
variations->axis[i].def ) / 2;
if ( FT_Set_Var_Design_Coordinates( face,
FT_UInt( coords.size() ),
coords.data() ) )
return;
}
// the interface function to the libFuzzer library
extern "C" int
LLVMFuzzerTestOneInput( const uint8_t* data,
size_t size_ )
{
assert( !InitResult );
if ( size_ < 1 )
return 0;
const vector<vector<FT_Byte>>& files = parse_data( data, size_ );
FT_Face face;
FT_Int32 load_flags = FT_LOAD_DEFAULT;
#if 0
FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL;
#endif
// We use a conservative approach here, at the cost of calling
// `FT_New_Face' quite often. The idea is that the fuzzer should be
// able to try all faces and named instances of a font, expecting that
// some faces don't work for various reasons, e.g., a broken subfont, or
// an unsupported NFNT bitmap font in a Mac dfont resource that holds
// more than a single font.
// get number of faces
if ( FT_New_Memory_Face( library,
files[0].data(),
(FT_Long)files[0].size(),
-1,
&face ) )
return 0;
long num_faces = face->num_faces;
FT_Done_Face( face );
// loop over up to 20 arbitrarily selected faces
// from index range [0;num-faces-1]
long max_face_cnt = num_faces < 20
? num_faces
: 20;
Random faces_pool( (int)max_face_cnt, (int)num_faces );
for ( long face_cnt = 0;
face_cnt < max_face_cnt;
face_cnt++ )
{
long face_index = faces_pool.get() - 1;
// get number of instances
if ( FT_New_Memory_Face( library,
files[0].data(),
(FT_Long)files[0].size(),
-( face_index + 1 ),
&face ) )
continue;
long num_instances = face->style_flags >> 16;
FT_Done_Face( face );
// loop over the face without instance (index 0)
// and up to 20 arbitrarily selected instances
// from index range [1;num_instances]
long max_instance_cnt = num_instances < 20
? num_instances
: 20;
Random instances_pool( (int)max_instance_cnt, (int)num_instances );
for ( long instance_cnt = 0;
instance_cnt <= max_instance_cnt;
instance_cnt++ )
{
long instance_index = 0;
if ( !instance_cnt )
{
if ( FT_New_Memory_Face( library,
files[0].data(),
(FT_Long)files[0].size(),
face_index,
&face ) )
continue;
}
else
{
instance_index = instances_pool.get();
if ( FT_New_Memory_Face( library,
files[0].data(),
(FT_Long)files[0].size(),
( instance_index << 16 ) + face_index,
&face ) )
continue;
}
// if we have more than a single input file coming from an archive,
// attach them (starting with the second file) using the order given
// in the archive
for ( size_t files_index = 1;
files_index < files.size();
files_index++ )
{
FT_Open_Args open_args = {};
open_args.flags = FT_OPEN_MEMORY;
open_args.memory_base = files[files_index].data();
open_args.memory_size = (FT_Long)files[files_index].size();
// the last archive element will be eventually used as the
// attachment
FT_Attach_Stream( face, &open_args );
}
// loop over an arbitrary size for outlines
// and up to ten arbitrarily selected bitmap strike sizes
// from the range [0;num_fixed_sizes - 1]
int max_size_cnt = face->num_fixed_sizes < 10
? face->num_fixed_sizes
: 10;
Random sizes_pool( max_size_cnt, face->num_fixed_sizes );
for ( int size_cnt = 0;
size_cnt <= max_size_cnt;
size_cnt++ )
{
FT_Int32 flags = load_flags;
int size_index = 0;
if ( !size_cnt )
{
// set up 20pt at 72dpi as an arbitrary size
if ( FT_Set_Char_Size( face, 20 * 64, 20 * 64, 72, 72 ) )
continue;
flags |= FT_LOAD_NO_BITMAP;
}
else
{
// bitmap strikes are not active for font variations
if ( instance_index )
continue;
size_index = sizes_pool.get() - 1;
if ( FT_Select_Size( face, size_index ) )
continue;
flags |= FT_LOAD_COLOR;
}
// test MM interface only for a face without a selected instance
// and without a selected bitmap strike
if ( !instance_index && !size_cnt )
setIntermediateAxis( face );
// loop over all glyphs
for ( unsigned int glyph_index = 0;
glyph_index < (unsigned int)face->num_glyphs;
glyph_index++ )
{
if ( FT_Load_Glyph( face, glyph_index, flags ) )
continue;
// Rendering is the most expensive and the least interesting part.
//
// if ( FT_Render_Glyph( face->glyph, render_mode) )
// continue;
// FT_GlyphSlot_Embolden( face->glyph );
#if 0
FT_Glyph glyph;
if ( !FT_Get_Glyph( face->glyph, &glyph ) )
FT_Done_Glyph( glyph );
FT_Outline* outline = &face->glyph->outline;
FT_Matrix rot30 = { 0xDDB4, -0x8000, 0x8000, 0xDDB4 };
FT_Outline_Transform( outline, &rot30 );
FT_BBox bbox;
FT_Outline_Get_BBox( outline, &bbox );
#endif
}
}
FT_Done_Face( face );
}
}
return 0;
}
// END