Skip to content

Commit

Permalink
fix(node): use cppgc for managing X509Certificate (denoland#21999)
Browse files Browse the repository at this point in the history
Introduces the first cppgc backed Resource into Deno.

This fixes the memory leak when using `X509Certificate`

**Comparison**:

```js
import { X509Certificate } from 'node:crypto';

const r = Deno.readFileSync('cli/tests/node_compat/test/fixtures/keys/agent1-cert.pem');

setInterval(() => {
  for (let i = 0; i < 10000; i++) {
    const cert = new X509Certificate(r);
  }
}, 1000);
```

Memory usage after 5 secs

`main`: 1692MB
`cppgc`: peaks at 400MB
  • Loading branch information
littledivy authored Jan 20, 2024
1 parent 40febd9 commit 28f6417
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 91 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ repository = "https://github.com/denoland/deno"

[workspace.dependencies]
deno_ast = { version = "1.0.1", features = ["transpiling"] }
deno_core = { version = "0.247.0" }
deno_core = { version = "0.248.0" }

deno_runtime = { version = "0.140.0", path = "./runtime" }
napi_sym = { version = "0.62.0", path = "./cli/napi/sym" }
Expand Down
125 changes: 41 additions & 84 deletions ext/node/ops/crypto/x509.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use deno_core::error::bad_resource_id;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;

use std::borrow::Cow;
use deno_core::v8;

use x509_parser::der_parser::asn1_rs::Any;
use x509_parser::der_parser::asn1_rs::Tag;
Expand Down Expand Up @@ -48,17 +45,11 @@ impl std::ops::Deref for Certificate {
}
}

impl Resource for Certificate {
fn name(&self) -> Cow<str> {
"x509Certificate".into()
}
}

#[op2(fast)]
pub fn op_node_x509_parse(
state: &mut OpState,
#[op2]
pub fn op_node_x509_parse<'s>(
scope: &'s mut v8::HandleScope,
#[buffer] buf: &[u8],
) -> Result<u32, AnyError> {
) -> Result<v8::Local<'s, v8::Object>, AnyError> {
let pem = match pem::parse_x509_pem(buf) {
Ok((_, pem)) => Some(pem),
Err(_) => None,
Expand All @@ -76,32 +67,25 @@ pub fn op_node_x509_parse(
cert: unsafe { std::mem::transmute(cert) },
pem,
};
let rid = state.resource_table.add(cert);
Ok(rid)

let obj = deno_core::cppgc::make_cppgc_object(scope, cert);
Ok(obj)
}

#[op2(fast)]
pub fn op_node_x509_ca(
state: &mut OpState,
rid: u32,
) -> Result<bool, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
pub fn op_node_x509_ca(rid: v8::Local<v8::Object>) -> Result<bool, AnyError> {
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.is_ca())
}

#[op2(fast)]
pub fn op_node_x509_check_email(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
#[string] email: &str,
) -> Result<bool, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;

let subject = cert.subject();
if subject
Expand Down Expand Up @@ -137,65 +121,50 @@ pub fn op_node_x509_check_email(
#[op2]
#[string]
pub fn op_node_x509_fingerprint(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<Option<String>, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.fingerprint::<sha1::Sha1>())
}

#[op2]
#[string]
pub fn op_node_x509_fingerprint256(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<Option<String>, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.fingerprint::<sha2::Sha256>())
}

#[op2]
#[string]
pub fn op_node_x509_fingerprint512(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<Option<String>, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.fingerprint::<sha2::Sha512>())
}

#[op2]
#[string]
pub fn op_node_x509_get_issuer(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<String, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(x509name_to_string(cert.issuer(), oid_registry())?)
}

#[op2]
#[string]
pub fn op_node_x509_get_subject(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<String, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(x509name_to_string(cert.subject(), oid_registry())?)
}

Expand Down Expand Up @@ -262,53 +231,41 @@ fn x509name_to_string(
#[op2]
#[string]
pub fn op_node_x509_get_valid_from(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<String, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.validity().not_before.to_string())
}

#[op2]
#[string]
pub fn op_node_x509_get_valid_to(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<String, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
Ok(cert.validity().not_after.to_string())
}

#[op2]
#[string]
pub fn op_node_x509_get_serial_number(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<String, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;
let mut s = cert.serial.to_str_radix(16);
s.make_ascii_uppercase();
Ok(s)
}

#[op2(fast)]
pub fn op_node_x509_key_usage(
state: &mut OpState,
rid: u32,
rid: v8::Local<v8::Object>,
) -> Result<u16, AnyError> {
let cert = state
.resource_table
.get::<Certificate>(rid)
.map_err(|_| bad_resource_id())?;
let cert = deno_core::cppgc::unwrap_cppgc_object::<Certificate>(rid)
.ok_or_else(bad_resource_id)?;

let key_usage = cert
.extensions()
Expand Down

0 comments on commit 28f6417

Please sign in to comment.