#ifndef HB_SANITIZE_HH
#define HB_SANITIZE_HH
#include "hb.hh"
#include "hb-blob.hh"
#include "hb-dispatch.hh"
#ifndef HB_SANITIZE_MAX_EDITS
#define HB_SANITIZE_MAX_EDITS 32
#endif
#ifndef HB_SANITIZE_MAX_OPS_FACTOR
#define HB_SANITIZE_MAX_OPS_FACTOR 64
#endif
#ifndef HB_SANITIZE_MAX_OPS_MIN
#define HB_SANITIZE_MAX_OPS_MIN 16384
#endif
#ifndef HB_SANITIZE_MAX_OPS_MAX
#define HB_SANITIZE_MAX_OPS_MAX 0x3FFFFFFF
#endif
#ifndef HB_SANITIZE_MAX_SUBTABLES
#define HB_SANITIZE_MAX_SUBTABLES 0x4000
#endif
struct hb_sanitize_context_t :
hb_dispatch_context_t<hb_sanitize_context_t, bool, HB_DEBUG_SANITIZE>
{
hb_sanitize_context_t () :
start (nullptr), end (nullptr),
length (0),
max_ops (0), max_subtables (0),
recursion_depth (0),
writable (false), edit_count (0),
blob (nullptr),
num_glyphs (65536),
num_glyphs_set (false),
lazy_some_gpos (false) {}
const char *get_name () { return "SANITIZE"; }
template <typename T, typename F>
bool may_dispatch (const T *obj HB_UNUSED, const F *format)
{
return format->sanitize (this) &&
hb_barrier ();
}
static return_t default_return_value () { return true; }
static return_t no_dispatch_return_value () { return false; }
bool stop_sublookup_iteration (const return_t r) const { return !r; }
bool visit_subtables (unsigned count)
{
max_subtables += count;
return max_subtables < HB_SANITIZE_MAX_SUBTABLES;
}
private:
template <typename T, typename ...Ts> auto
_dispatch (const T &obj, hb_priority<1>, Ts&&... ds) HB_AUTO_RETURN
( obj.sanitize (this, std::forward<Ts> (ds)...) )
template <typename T, typename ...Ts> auto
_dispatch (const T &obj, hb_priority<0>, Ts&&... ds) HB_AUTO_RETURN
( obj.dispatch (this, std::forward<Ts> (ds)...) )
public:
template <typename T, typename ...Ts> auto
dispatch (const T &obj, Ts&&... ds) HB_AUTO_RETURN
( _dispatch (obj, hb_prioritize, std::forward<Ts> (ds)...) )
hb_sanitize_context_t (hb_blob_t *b) : hb_sanitize_context_t ()
{
init (b);
if (blob)
start_processing ();
}
~hb_sanitize_context_t ()
{
if (blob)
end_processing ();
}
void init (hb_blob_t *b)
{
this->blob = hb_blob_reference (b);
this->writable = false;
}
void set_num_glyphs (unsigned int num_glyphs_)
{
num_glyphs = num_glyphs_;
num_glyphs_set = true;
}
unsigned int get_num_glyphs () { return num_glyphs; }
void set_max_ops (int max_ops_) { max_ops = max_ops_; }
template <typename T>
void set_object (const T *obj)
{
reset_object ();
if (!obj) return;
const char *obj_start = (const char *) obj;
if (unlikely (obj_start < this->start || this->end <= obj_start))
{
this->start = this->end = nullptr;
this->length = 0;
}
else
{
this->start = obj_start;
this->end = obj_start + hb_min (size_t (this->end - obj_start), obj->get_size ());
this->length = this->end - this->start;
}
}
void reset_object ()
{
this->start = this->blob->data;
this->end = this->start + this->blob->length;
this->length = this->end - this->start;
assert (this->start <= this->end);
}
void start_processing ()
{
reset_object ();
unsigned m;
if (unlikely (hb_unsigned_mul_overflows (this->end - this->start, HB_SANITIZE_MAX_OPS_FACTOR, &m)))
this->max_ops = HB_SANITIZE_MAX_OPS_MAX;
else
this->max_ops = hb_clamp (m,
(unsigned) HB_SANITIZE_MAX_OPS_MIN,
(unsigned) HB_SANITIZE_MAX_OPS_MAX);
this->edit_count = 0;
this->debug_depth = 0;
this->recursion_depth = 0;
DEBUG_MSG_LEVEL (SANITIZE, start, 0, +1,
"start [%p..%p] (%lu bytes)",
this->start, this->end,
(unsigned long) (this->end - this->start));
}
void end_processing ()
{
DEBUG_MSG_LEVEL (SANITIZE, this->start, 0, -1,
"end [%p..%p] %u edit requests",
this->start, this->end, this->edit_count);
hb_blob_destroy (this->blob);
this->blob = nullptr;
this->start = this->end = nullptr;
this->length = 0;
}
unsigned get_edit_count () { return edit_count; }
bool check_ops(unsigned count)
{
if (unlikely (this->max_ops < 0 || count >= (unsigned) this->max_ops))
{
this->max_ops = -1;
return false;
}
this->max_ops -= (int) count;
return true;
}
#ifndef HB_OPTIMIZE_SIZE
HB_ALWAYS_INLINE
#endif
bool check_range (const void *base,
unsigned int len) const
{
const char *p = (const char *) base;
bool ok = (uintptr_t) (p - this->start) <= this->length &&
(unsigned int) (this->end - p) >= len &&
((this->max_ops -= len) > 0);
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"check_range [%p..%p]"
" (%u bytes) in [%p..%p] -> %s",
p, p + len, len,
this->start, this->end,
ok ? "OK" : "OUT-OF-RANGE");
return likely (ok);
}
#ifndef HB_OPTIMIZE_SIZE
HB_ALWAYS_INLINE
#endif
bool check_range_fast (const void *base,
unsigned int len) const
{
const char *p = (const char *) base;
bool ok = ((uintptr_t) (p - this->start) <= this->length &&
(unsigned int) (this->end - p) >= len);
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"check_range_fast [%p..%p]"
" (%u bytes) in [%p..%p] -> %s",
p, p + len, len,
this->start, this->end,
ok ? "OK" : "OUT-OF-RANGE");
return likely (ok);
}
#ifndef HB_OPTIMIZE_SIZE
HB_ALWAYS_INLINE
#endif
bool check_point (const void *base) const
{
const char *p = (const char *) base;
bool ok = (uintptr_t) (p - this->start) <= this->length;
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"check_point [%p]"
" in [%p..%p] -> %s",
p,
this->start, this->end,
ok ? "OK" : "OUT-OF-RANGE");
return likely (ok);
}
template <typename T>
bool check_range (const T *base,
unsigned int a,
unsigned int b) const
{
unsigned m;
return !hb_unsigned_mul_overflows (a, b, &m) &&
this->check_range (base, m);
}
template <typename T>
bool check_range (const T *base,
unsigned int a,
unsigned int b,
unsigned int c) const
{
unsigned m;
return !hb_unsigned_mul_overflows (a, b, &m) &&
this->check_range (base, m, c);
}
template <typename T>
HB_ALWAYS_INLINE
bool check_array_sized (const T *base, unsigned int len, unsigned len_size) const
{
if (len_size >= 4)
{
if (unlikely (hb_unsigned_mul_overflows (len, hb_static_size (T), &len)))
return false;
}
else
len = len * hb_static_size (T);
return this->check_range (base, len);
}
template <typename T>
bool check_array (const T *base, unsigned int len) const
{
return this->check_range (base, len, hb_static_size (T));
}
template <typename T>
bool check_array (const T *base,
unsigned int a,
unsigned int b) const
{
return this->check_range (base, hb_static_size (T), a, b);
}
bool check_start_recursion (int max_depth)
{
if (unlikely (recursion_depth >= max_depth)) return false;
return ++recursion_depth;
}
bool end_recursion (bool result)
{
recursion_depth--;
return result;
}
template <typename Type>
#ifndef HB_OPTIMIZE_SIZE
HB_ALWAYS_INLINE
#endif
bool check_struct (const Type *obj) const
{
if (sizeof (uintptr_t) == sizeof (uint32_t))
return likely (this->check_range_fast (obj, obj->min_size));
else
return likely (this->check_point ((const char *) obj + obj->min_size));
}
bool may_edit (const void *base, unsigned int len)
{
if (this->edit_count >= HB_SANITIZE_MAX_EDITS)
return false;
const char *p = (const char *) base;
this->edit_count++;
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"may_edit(%u) [%p..%p] (%u bytes) in [%p..%p] -> %s",
this->edit_count,
p, p + len, len,
this->start, this->end,
this->writable ? "GRANTED" : "DENIED");
return this->writable;
}
template <typename Type, typename ValueType>
bool try_set (const Type *obj, const ValueType &v)
{
if (this->may_edit (obj, hb_static_size (Type)))
{
* const_cast<Type *> (obj) = v;
return true;
}
return false;
}
template <typename Type>
hb_blob_t *sanitize_blob (hb_blob_t *blob)
{
bool sane;
init (blob);
retry:
DEBUG_MSG_FUNC (SANITIZE, start, "start");
start_processing ();
if (unlikely (!start))
{
end_processing ();
return blob;
}
Type *t = reinterpret_cast<Type *> (const_cast<char *> (start));
sane = t->sanitize (this);
if (sane)
{
if (edit_count)
{
DEBUG_MSG_FUNC (SANITIZE, start, "passed first round with %u edits; going for second round", edit_count);
edit_count = 0;
sane = t->sanitize (this);
if (edit_count) {
DEBUG_MSG_FUNC (SANITIZE, start, "requested %u edits in second round; FAILING", edit_count);
sane = false;
}
}
}
else
{
if (edit_count && !writable) {
start = hb_blob_get_data_writable (blob, nullptr);
end = start + blob->length;
if (start)
{
writable = true;
DEBUG_MSG_FUNC (SANITIZE, start, "retry");
goto retry;
}
}
}
end_processing ();
DEBUG_MSG_FUNC (SANITIZE, start, sane ? "PASSED" : "FAILED");
if (sane)
{
hb_blob_make_immutable (blob);
return blob;
}
else
{
hb_blob_destroy (blob);
return hb_blob_get_empty ();
}
}
template <typename Type>
hb_blob_t *reference_table (const hb_face_t *face, hb_tag_t tableTag = Type::tableTag)
{
if (!num_glyphs_set)
set_num_glyphs (hb_face_get_glyph_count (face));
return sanitize_blob<Type> (hb_face_reference_table (face, tableTag));
}
const char *start, *end;
unsigned length;
mutable int max_ops, max_subtables;
private:
int recursion_depth;
bool writable;
unsigned int edit_count;
hb_blob_t *blob;
unsigned int num_glyphs;
bool num_glyphs_set;
public:
bool lazy_some_gpos;
};
struct hb_sanitize_with_object_t
{
template <typename T>
hb_sanitize_with_object_t (hb_sanitize_context_t *c, const T& obj) : c (c)
{ c->set_object (obj); }
~hb_sanitize_with_object_t ()
{ c->reset_object (); }
private:
hb_sanitize_context_t *c;
};
#endif