Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
### Changed
### Added
### Fixed
* Check a type tag before unwrapping a native object, so a forged prototype chain can no longer crash the process (#2628)
* Load images from Node.js object URLs (#2525)

3.2.3
Expand Down
4 changes: 3 additions & 1 deletion src/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Canvas::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

Canvas::Canvas(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Canvas>(info), env(info.Env()) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.Canvas);
InstanceData* data = env.GetInstanceData<InstanceData>();
ctor = Napi::Persistent(data->CanvasCtor.Value());

Expand Down Expand Up @@ -722,7 +723,8 @@ Canvas::resurface(Napi::Object This, uint16_t width, uint16_t height) {
this->width = width;
this->height = height;
ensureSurface();
if (This.Get("context").UnwrapTo(&context) && context.IsObject()) {
if (This.Get("context").UnwrapTo(&context) && context.IsObject()
&& context.As<Napi::Object>().CheckTypeTag(&env.GetInstanceData<InstanceData>()->tags.Context2d)) {
// Reset context
Context2d *context2d = Context2d::Unwrap(context.As<Napi::Object>());
cairo_t *prev = context2d->context();
Expand Down
1 change: 1 addition & 0 deletions src/CanvasGradient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Gradient::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

Gradient::Gradient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Gradient>(info), env(info.Env()) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.Gradient);
// Linear
if (
4 == info.Length() &&
Expand Down
5 changes: 3 additions & 2 deletions src/CanvasPattern.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Pattern::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap<Pattern>(info), env(info.Env()) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.Pattern);
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
return;
Expand All @@ -38,7 +39,7 @@ Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap<Pattern>(info), en
InstanceData* data = env.GetInstanceData<InstanceData>();

// Image
if (obj.InstanceOf(data->ImageCtor.Value()).UnwrapOr(false)) {
if (obj.CheckTypeTag(&data->tags.Image)) {
image = Image::Unwrap(obj);
if (!image->isComplete()) {
Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException();
Expand All @@ -47,7 +48,7 @@ Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap<Pattern>(info), en
value = Napi::Persistent(obj);

// Canvas
} else if (obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) {
} else if (obj.CheckTypeTag(&data->tags.Canvas)) {
canvas = Canvas::Unwrap(obj);
value = Napi::Persistent(obj);
// Invalid
Expand Down
17 changes: 9 additions & 8 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Context2d::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Context2d>(info), env(info.Env()) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.Context2d);
InstanceData* data = env.GetInstanceData<InstanceData>();

if (!info[0].IsObject()) {
Expand All @@ -175,7 +176,7 @@ Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Context2
}

Napi::Object obj = info[0].As<Napi::Object>();
if (!obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) {
if (!obj.CheckTypeTag(&data->tags.Canvas)) {
if (!env.IsExceptionPending()) {
Napi::TypeError::New(env, "Canvas expected").ThrowAsJavaScriptException();
}
Expand Down Expand Up @@ -761,7 +762,7 @@ Context2d::PutImageData(const Napi::CallbackInfo& info) {
}
Napi::Object obj = info[0].As<Napi::Object>();
InstanceData* data = env.GetInstanceData<InstanceData>();
if (!obj.InstanceOf(data->ImageDataCtor.Value()).UnwrapOr(false)) {
if (!obj.CheckTypeTag(&data->tags.ImageData)) {
if (!env.IsExceptionPending()) {
Napi::TypeError::New(env, "ImageData expected").ThrowAsJavaScriptException();
}
Expand Down Expand Up @@ -1259,7 +1260,7 @@ Context2d::DrawImage(const Napi::CallbackInfo& info) {
Napi::Object obj = info[0].As<Napi::Object>();

// Image
if (obj.InstanceOf(env.GetInstanceData<InstanceData>()->ImageCtor.Value()).UnwrapOr(false)) {
if (obj.CheckTypeTag(&env.GetInstanceData<InstanceData>()->tags.Image)) {
Image *img = Image::Unwrap(obj);
if (!img->isComplete()) {
Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException();
Expand All @@ -1270,7 +1271,7 @@ Context2d::DrawImage(const Napi::CallbackInfo& info) {
surface = img->surface.surface();

// Canvas
} else if (obj.InstanceOf(env.GetInstanceData<InstanceData>()->CanvasCtor.Value()).UnwrapOr(false)) {
} else if (obj.CheckTypeTag(&env.GetInstanceData<InstanceData>()->tags.Canvas)) {
Canvas *canvas = Canvas::Unwrap(obj);
source_w = sw = canvas->getWidth();
source_h = sh = canvas->getHeight();
Expand Down Expand Up @@ -1903,9 +1904,9 @@ Context2d::SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value
} else if (value.IsObject()) {
InstanceData *data = env.GetInstanceData<InstanceData>();
Napi::Object obj = value.As<Napi::Object>();
if (obj.InstanceOf(data->CanvasGradientCtor.Value()).UnwrapOr(false)) {
if (obj.CheckTypeTag(&data->tags.Gradient)) {
state->fillGradient.Reset(obj);
} else if (obj.InstanceOf(data->CanvasPatternCtor.Value()).UnwrapOr(false)) {
} else if (obj.CheckTypeTag(&data->tags.Pattern)) {
state->fillPattern.Reset(obj);
}
}
Expand Down Expand Up @@ -1940,9 +1941,9 @@ Context2d::SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& val
} else if (value.IsObject()) {
InstanceData *data = env.GetInstanceData<InstanceData>();
Napi::Object obj = value.As<Napi::Object>();
if (obj.InstanceOf(data->CanvasGradientCtor.Value()).UnwrapOr(false)) {
if (obj.CheckTypeTag(&data->tags.Gradient)) {
state->strokeGradient.Reset(obj);
} else if (obj.InstanceOf(data->CanvasPatternCtor.Value()).UnwrapOr(false)) {
} else if (obj.CheckTypeTag(&data->tags.Pattern)) {
state->strokePattern.Reset(obj);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/FontFace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ FontFace::FontFace(const Napi::CallbackInfo& info) :
id(++lastId),
env(info.Env())
{
info.This().As<Napi::Object>().TypeTag(&env.GetInstanceData<InstanceData>()->tags.FontFace);

if (info.Length() < 2) {
Napi::TypeError::New(env, "Family and source arguments are required").ThrowAsJavaScriptException();
return;
Expand Down
7 changes: 4 additions & 3 deletions src/FontFaceSet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ FontFaceSet::FontFaceSet(Napi::CallbackInfo& info) :
env(info.Env()),
ready(Napi::Promise::Deferred::New(info.Env())),
Napi::ObjectWrap<FontFaceSet>(info) {
info.This().As<Napi::Object>().TypeTag(&env.GetInstanceData<InstanceData>()->tags.FontFaceSet);
}

void
Expand Down Expand Up @@ -52,7 +53,7 @@ FontFaceSet::Add(const Napi::CallbackInfo& info) {
isFontFace = false;
} else {
obj = info[0].As<Napi::Object>();
if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined();
isFontFace = obj.CheckTypeTag(&data->tags.FontFace);
}
if (!isFontFace) {
Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException();
Expand Down Expand Up @@ -85,7 +86,7 @@ FontFaceSet::Has(const Napi::CallbackInfo& info) {
isFontFace = false;
} else {
obj = info[0].As<Napi::Object>();
if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined();
isFontFace = obj.CheckTypeTag(&data->tags.FontFace);
}
if (!isFontFace) {
Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException();
Expand Down Expand Up @@ -124,7 +125,7 @@ FontFaceSet::Delete(const Napi::CallbackInfo& info) {
isFontFace = false;
} else {
obj = info[0].As<Napi::Object>();
if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined();
isFontFace = obj.CheckTypeTag(&data->tags.FontFace);
}
if (!isFontFace) {
Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException();
Expand Down
12 changes: 11 additions & 1 deletion src/Image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,7 @@ Image::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

Image::Image(const Napi::CallbackInfo& info) : ObjectWrap<Image>(info), env(info.Env()) , surface(env) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.Image);
info.This().ToObject().Unwrap().Set("onload", env.Null());
info.This().ToObject().Unwrap().Set("onerror", env.Null());
}
Expand Down Expand Up @@ -1586,7 +1587,12 @@ Image::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) {
Napi::Value
Image::GetSource(const Napi::CallbackInfo& info){
Napi::Env env = info.Env();
Image *img = Image::Unwrap(info.This().As<Napi::Object>());
Napi::Object This = info.This().As<Napi::Object>();
if (!This.CheckTypeTag(&env.GetInstanceData<InstanceData>()->tags.Image)) {
Napi::TypeError::New(env, "Image expected").ThrowAsJavaScriptException();
return env.Undefined();
}
Image *img = Image::Unwrap(This);
return Napi::String::New(env, img->surface.filename ? img->surface.filename : "");
}

Expand All @@ -1598,6 +1604,10 @@ void
Image::SetSource(const Napi::CallbackInfo& info){
Napi::Env env = info.Env();
Napi::Object This = info.This().As<Napi::Object>();
if (!This.CheckTypeTag(&env.GetInstanceData<InstanceData>()->tags.Image)) {
Napi::TypeError::New(env, "Image expected").ThrowAsJavaScriptException();
return;
}
Image *img = Image::Unwrap(This);
cairo_status_t status = CAIRO_STATUS_READ_ERROR;

Expand Down
1 change: 1 addition & 0 deletions src/ImageData.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ImageData::Initialize(Napi::Env& env, Napi::Object& exports) {
*/

ImageData::ImageData(const Napi::CallbackInfo& info) : Napi::ObjectWrap<ImageData>(info), env(info.Env()) {
info.This().As<Napi::Object>().TypeTag(&info.Env().GetInstanceData<InstanceData>()->tags.ImageData);
Napi::TypedArray dataArray;
uint32_t width;
uint32_t height;
Expand Down
24 changes: 24 additions & 0 deletions src/InstanceData.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <napi.h>
#include <cstdint>
#include <random>
#include <freetype/freetype.h>
#include "FontFaceSet.h"
#ifdef _WIN32
Expand All @@ -15,7 +17,29 @@ using PlatformFontManager = FontManagerMacos;
using PlatformFontManager = FontManagerLinux;
#endif

// Per-class type tags; applied on construction and checked before napi_unwrap.
struct TypeTags {
napi_type_tag Canvas;
napi_type_tag Context2d;
napi_type_tag Image;
napi_type_tag ImageData;
napi_type_tag Gradient;
napi_type_tag Pattern;
napi_type_tag FontFace;
napi_type_tag FontFaceSet;

TypeTags() {
std::random_device rd;
for (napi_type_tag* tag : {&Canvas, &Context2d, &Image, &ImageData,
&Gradient, &Pattern, &FontFace, &FontFaceSet}) {
tag->lower = (static_cast<uint64_t>(rd()) << 32) | rd();
tag->upper = (static_cast<uint64_t>(rd()) << 32) | rd();
}
}
};

struct InstanceData {
TypeTags tags;
Napi::FunctionReference CanvasCtor;
Napi::FunctionReference CanvasGradientCtor;
Napi::FunctionReference DOMMatrixCtor;
Expand Down