Skip to content

Commit

Permalink
src: prevent URLPattern property getters from crashing with invalid this
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Feb 2, 2025
1 parent 2bd5694 commit bbf2adf
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@
V(qlogoutputstream_constructor_template, v8::ObjectTemplate) \
V(tcp_constructor_template, v8::FunctionTemplate) \
V(tty_constructor_template, v8::FunctionTemplate) \
V(urlpattern_constructor_template, v8::FunctionTemplate) \
V(write_wrap_template, v8::ObjectTemplate) \
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
V(x509_constructor_template, v8::FunctionTemplate)
Expand Down
177 changes: 113 additions & 64 deletions src/node_url_pattern.cc
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,10 @@ void URLPattern::Test(const FunctionCallbackInfo<Value>& args) {
}

void URLPattern::Protocol(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -625,6 +629,10 @@ void URLPattern::Protocol(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Username(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -634,6 +642,10 @@ void URLPattern::Username(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Password(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -643,6 +655,10 @@ void URLPattern::Password(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Hostname(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -652,6 +668,10 @@ void URLPattern::Hostname(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Port(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -661,6 +681,10 @@ void URLPattern::Port(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Pathname(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -670,6 +694,10 @@ void URLPattern::Pathname(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Search(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -679,6 +707,10 @@ void URLPattern::Search(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::Hash(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
Local<Value> result;
Expand All @@ -688,6 +720,10 @@ void URLPattern::Hash(const FunctionCallbackInfo<Value>& info) {
}

void URLPattern::HasRegexpGroups(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
if (!HasInstance(env, info.This())) {
return THROW_ERR_INVALID_THIS(env);
}
URLPattern* url_pattern;
ASSIGN_OR_RETURN_UNWRAP(&url_pattern, info.This());
info.GetReturnValue().Set(url_pattern->HasRegExpGroups());
Expand All @@ -708,75 +744,88 @@ static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(URLPattern::Test);
}

Local<FunctionTemplate> URLPattern::GetConstructorTemplate(Environment* env) {
auto tmpl = env->urlpattern_constructor_template();
if (tmpl.IsEmpty()) {
auto attributes = static_cast<PropertyAttribute>(ReadOnly | DontDelete);
tmpl = NewFunctionTemplate(env->isolate(), URLPattern::New);
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "URLPattern"));
auto instance_template = tmpl->InstanceTemplate();
auto prototype_template = tmpl->PrototypeTemplate();

instance_template->SetInternalFieldCount(URLPattern::kInternalFieldCount);
prototype_template->SetAccessorProperty(
env->protocol_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Protocol),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->username_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Username),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->password_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Password),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->hostname_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Hostname),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->port_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Port),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->pathname_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Pathname),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->search_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Search),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->hash_string(),
FunctionTemplate::New(env->isolate(), URLPattern::Hash),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->has_regexp_groups_string(),
FunctionTemplate::New(env->isolate(), URLPattern::HasRegexpGroups),
Local<FunctionTemplate>(),
attributes);

SetProtoMethodNoSideEffect(env->isolate(), tmpl, "exec", URLPattern::Exec);
SetProtoMethodNoSideEffect(env->isolate(), tmpl, "test", URLPattern::Test);
env->set_urlpattern_constructor_template(tmpl);
}
return tmpl;
}

bool URLPattern::HasInstance(Environment* env, Local<Value> value) {
return GetConstructorTemplate(env)->HasInstance(value);
}

static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
Isolate* isolate = env->isolate();
auto attributes = static_cast<PropertyAttribute>(ReadOnly | DontDelete);
auto ctor_tmpl = NewFunctionTemplate(isolate, URLPattern::New);
auto instance_template = ctor_tmpl->InstanceTemplate();
auto prototype_template = ctor_tmpl->PrototypeTemplate();
ctor_tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "URLPattern"));

instance_template->SetInternalFieldCount(URLPattern::kInternalFieldCount);
prototype_template->SetAccessorProperty(
env->protocol_string(),
FunctionTemplate::New(isolate, URLPattern::Protocol),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->username_string(),
FunctionTemplate::New(isolate, URLPattern::Username),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->password_string(),
FunctionTemplate::New(isolate, URLPattern::Password),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->hostname_string(),
FunctionTemplate::New(isolate, URLPattern::Hostname),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->port_string(),
FunctionTemplate::New(isolate, URLPattern::Port),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->pathname_string(),
FunctionTemplate::New(isolate, URLPattern::Pathname),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->search_string(),
FunctionTemplate::New(isolate, URLPattern::Search),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->hash_string(),
FunctionTemplate::New(isolate, URLPattern::Hash),
Local<FunctionTemplate>(),
attributes);

prototype_template->SetAccessorProperty(
env->has_regexp_groups_string(),
FunctionTemplate::New(isolate, URLPattern::HasRegexpGroups),
Local<FunctionTemplate>(),
attributes);

SetProtoMethodNoSideEffect(isolate, ctor_tmpl, "exec", URLPattern::Exec);
SetProtoMethodNoSideEffect(isolate, ctor_tmpl, "test", URLPattern::Test);
auto ctor_tmpl = URLPattern::GetConstructorTemplate(env);
CHECK(!ctor_tmpl.IsEmpty());
SetConstructorFunction(context, target, "URLPattern", ctor_tmpl);
}

Expand Down
4 changes: 4 additions & 0 deletions src/node_url_pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class URLPatternRegexProvider {

class URLPattern : public BaseObject {
public:
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);

URLPattern(Environment* env,
v8::Local<v8::Object> object,
ada::url_pattern<URLPatternRegexProvider>&& url_pattern);
Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-urlpattern-invalidthis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

require('../common');

const { URLPattern } = require('url');
const { throws } = require('assert');

const pattern = new URLPattern();
const proto = Object.getPrototypeOf(pattern);

// Verifies that attempts to call the property getters on a URLPattern
// with the incorrect `this` will not crash the process.
[
'protocol',
'username',
'password',
'hostname',
'port',
'pathname',
'search',
'hash',
'hasRegExpGroups',
].forEach((i) => {
const prop = Object.getOwnPropertyDescriptor(proto, i).get;
throws(() => prop({}), {
code: 'ERR_INVALID_THIS',
}, i);
});

// Verifies that attempts to call the exec and test functions
// with the wrong this also throw

const { test, exec } = pattern;

throws(() => test({}), {
message: 'Illegal invocation',
});
throws(() => exec({}), {
message: 'Illegal invocation',
});

0 comments on commit bbf2adf

Please sign in to comment.