VYPR
Critical severityNVD Advisory· Published Mar 25, 2022· Updated Apr 23, 2025

Sandbox bypass leading to arbitrary code execution in Deno

CVE-2022-24783

Description

Deno is a runtime for JavaScript and TypeScript. The versions of Deno between release 1.18.0 and 1.20.2 (inclusive) are vulnerable to an attack where a malicious actor controlling the code executed in a Deno runtime could bypass all permission checks and execute arbitrary shell code. This vulnerability does not affect users of Deno Deploy. The vulnerability has been patched in Deno 1.20.3. There is no workaround. All users are recommended to upgrade to 1.20.3 immediately.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
denocrates.io
>= 1.18.0, < 1.20.31.20.3

Affected products

1

Patches

1
fcfce1bb869f

fix(ext/ffi): enforce unstable check on ops (#14115)

https://github.com/denoland/denoLuca CasonatoMar 25, 2022via ghsa
34 files changed · +249 8
  • cli/tests/integration/run_tests.rs+90 0 modified
    @@ -2523,3 +2523,93 @@ itest!(fetch_async_error_stack {
       output: "fetch_async_error_stack.ts.out",
       exit_code: 1,
     });
    +
    +itest!(unstable_ffi_1 {
    +  args: "run unstable_ffi_1.js",
    +  output: "unstable_ffi_1.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_2 {
    +  args: "run unstable_ffi_2.js",
    +  output: "unstable_ffi_2.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_3 {
    +  args: "run unstable_ffi_3.js",
    +  output: "unstable_ffi_3.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_4 {
    +  args: "run unstable_ffi_4.js",
    +  output: "unstable_ffi_4.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_5 {
    +  args: "run unstable_ffi_5.js",
    +  output: "unstable_ffi_5.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_6 {
    +  args: "run unstable_ffi_6.js",
    +  output: "unstable_ffi_6.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_7 {
    +  args: "run unstable_ffi_7.js",
    +  output: "unstable_ffi_7.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_8 {
    +  args: "run unstable_ffi_8.js",
    +  output: "unstable_ffi_8.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_9 {
    +  args: "run unstable_ffi_9.js",
    +  output: "unstable_ffi_9.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_10 {
    +  args: "run unstable_ffi_10.js",
    +  output: "unstable_ffi_10.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_11 {
    +  args: "run unstable_ffi_11.js",
    +  output: "unstable_ffi_11.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_12 {
    +  args: "run unstable_ffi_12.js",
    +  output: "unstable_ffi_12.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_13 {
    +  args: "run unstable_ffi_13.js",
    +  output: "unstable_ffi_13.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_14 {
    +  args: "run unstable_ffi_14.js",
    +  output: "unstable_ffi_14.js.out",
    +  exit_code: 70,
    +});
    +
    +itest!(unstable_ffi_15 {
    +  args: "run unstable_ffi_15.js",
    +  output: "unstable_ffi_15.js.out",
    +  exit_code: 70,
    +});
    
  • cli/tests/testdata/unstable_ffi_10.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_i16", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_10.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getInt16'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_11.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_u32", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_11.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getUint32'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_12.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_i32", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_12.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getInt32'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_13.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_u64", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_13.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getBigUint64'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_14.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_f32", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_14.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getFloat32'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_15.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_f64", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_15.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getFloat64'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_1.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_load", { path: "", symbols: {} });
    
  • cli/tests/testdata/unstable_ffi_1.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.dlopen'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_2.js+10 0 added
    @@ -0,0 +1,10 @@
    +Deno.core.opSync("op_ffi_call_ptr", {
    +  pointer: [0, 0],
    +  def: {
    +    name: null,
    +    parameters: [],
    +    result: "void",
    +  },
    +  parameters: [],
    +  buffers: [],
    +});
    
  • cli/tests/testdata/unstable_ffi_2.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafeFnPointer#call'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_3.js+10 0 added
    @@ -0,0 +1,10 @@
    +Deno.core.opAsync("op_ffi_call_ptr_nonblocking", {
    +  pointer: [0, 0],
    +  def: {
    +    name: null,
    +    parameters: [],
    +    result: "void",
    +  },
    +  parameters: [],
    +  buffers: [],
    +});
    
  • cli/tests/testdata/unstable_ffi_3.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafeFnPointer#call'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_4.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_ptr_of", new Uint8Array(0));
    
  • cli/tests/testdata/unstable_ffi_4.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointer#of'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_5.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_buf_copy_into", [[0, 0], new Uint8Array(0), 0]);
    
  • cli/tests/testdata/unstable_ffi_5.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#copyInto'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_6.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_cstr_read", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_6.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getCString'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_7.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_u8", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_7.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getUint8'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_8.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_i8", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_8.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getInt8'. The --unstable flag must be provided.
    
  • cli/tests/testdata/unstable_ffi_9.js+1 0 added
    @@ -0,0 +1 @@
    +Deno.core.opSync("op_ffi_read_u16", [0, 0]);
    
  • cli/tests/testdata/unstable_ffi_9.js.out+1 0 added
    @@ -0,0 +1 @@
    +Unstable API 'Deno.UnsafePointerView#getUint16'. The --unstable flag must be provided.
    
  • cli/tests/unit/ffi_test.ts+51 0 modified
    @@ -23,3 +23,54 @@ Deno.test({ permissions: { ffi: true } }, function dlopenInvalidArguments() {
         Deno.dlopen(filename);
       }, TypeError);
     });
    +
    +Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() {
    +  assertThrows(() => {
    +    Deno.dlopen("/usr/lib/libc.so.6", {});
    +  }, Deno.errors.PermissionDenied);
    +  const ptr = new Deno.UnsafePointer(0n);
    +  const fnptr = new Deno.UnsafeFnPointer(
    +    ptr,
    +    {
    +      parameters: ["u32", "pointer"],
    +      result: "void",
    +    } as const,
    +  );
    +  assertThrows(() => {
    +    fnptr.call(123, null);
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    Deno.UnsafePointer.of(new Uint8Array(0));
    +  }, Deno.errors.PermissionDenied);
    +  const ptrView = new Deno.UnsafePointerView(ptr);
    +  assertThrows(() => {
    +    ptrView.copyInto(new Uint8Array(0));
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getCString();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getUint8();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getInt8();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getUint16();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getInt16();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getUint32();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getInt32();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getFloat32();
    +  }, Deno.errors.PermissionDenied);
    +  assertThrows(() => {
    +    ptrView.getFloat64();
    +  }, Deno.errors.PermissionDenied);
    +});
    
  • ext/ffi/lib.rs+57 5 modified
    @@ -45,6 +45,11 @@ fn check_unstable(state: &OpState, api_name: &str) {
       }
     }
     
    +pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
    +  let state = state.borrow();
    +  check_unstable(&state, api_name)
    +}
    +
     pub trait FfiPermissions {
       fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError>;
     }
    @@ -144,8 +149,8 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
           op_ffi_get_static::decl(),
           op_ffi_call::decl(),
           op_ffi_call_nonblocking::decl(),
    -      op_ffi_call_ptr::decl(),
    -      op_ffi_call_ptr_nonblocking::decl(),
    +      op_ffi_call_ptr::decl::<P>(),
    +      op_ffi_call_ptr_nonblocking::decl::<P>(),
           op_ffi_ptr_of::decl::<P>(),
           op_ffi_buf_copy_into::decl::<P>(),
           op_ffi_cstr_read::decl::<P>(),
    @@ -648,15 +653,38 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
     }
     
     #[op]
    -fn op_ffi_call_ptr(args: FfiCallPtrArgs) -> Result<Value, AnyError> {
    +fn op_ffi_call_ptr<FP>(
    +  state: &mut deno_core::OpState,
    +  args: FfiCallPtrArgs,
    +) -> Result<Value, AnyError>
    +where
    +  FP: FfiPermissions + 'static,
    +{
    +  check_unstable(state, "Deno.UnsafeFnPointer#call");
    +
    +  let permissions = state.borrow_mut::<FP>();
    +  permissions.check(None)?;
    +
       let symbol = args.get_symbol();
       ffi_call(args.into(), &symbol)
     }
     
     #[op]
    -async fn op_ffi_call_ptr_nonblocking(
    +async fn op_ffi_call_ptr_nonblocking<FP>(
    +  state: Rc<RefCell<deno_core::OpState>>,
       args: FfiCallPtrArgs,
    -) -> Result<Value, AnyError> {
    +) -> Result<Value, AnyError>
    +where
    +  FP: FfiPermissions + 'static,
    +{
    +  check_unstable2(&state, "Deno.UnsafeFnPointer#call");
    +
    +  {
    +    let mut state = state.borrow_mut();
    +    let permissions = state.borrow_mut::<FP>();
    +    permissions.check(None)?;
    +  }
    +
       let symbol = args.get_symbol();
       tokio::task::spawn_blocking(move || ffi_call(args.into(), &symbol))
         .await
    @@ -774,6 +802,8 @@ fn op_ffi_ptr_of<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointer#of");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -788,6 +818,8 @@ fn op_ffi_buf_copy_into<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#copyInto");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -810,6 +842,8 @@ fn op_ffi_cstr_read<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getCString");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -825,6 +859,8 @@ fn op_ffi_read_u8<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getUint8");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -839,6 +875,8 @@ fn op_ffi_read_i8<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getInt8");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -853,6 +891,8 @@ fn op_ffi_read_u16<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getUint16");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -867,6 +907,8 @@ fn op_ffi_read_i16<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getInt16");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -881,6 +923,8 @@ fn op_ffi_read_u32<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getUint32");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -895,6 +939,8 @@ fn op_ffi_read_i32<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getInt32");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -909,6 +955,8 @@ fn op_ffi_read_u64<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -925,6 +973,8 @@ fn op_ffi_read_f32<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getFloat32");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    @@ -939,6 +989,8 @@ fn op_ffi_read_f64<FP>(
     where
       FP: FfiPermissions + 'static,
     {
    +  check_unstable(state, "Deno.UnsafePointerView#getFloat64");
    +
       let permissions = state.borrow_mut::<FP>();
       permissions.check(None)?;
     
    
  • .github/workflows/ci.yml+3 3 modified
    @@ -236,7 +236,7 @@ jobs:
                 ~/.cargo/registry/index
                 ~/.cargo/registry/cache
                 ~/.cargo/git/db
    -          key: 4-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
    +          key: 5-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
     
           # In main branch, always creates fresh cache
           - name: Cache build output (main)
    @@ -252,7 +252,7 @@ jobs:
                 !./target/*/*.zip
                 !./target/*/*.tar.gz
               key: |
    -            4-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
    +            5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
     
           # Restore cache from the latest 'main' branch build.
           - name: Cache build output (PR)
    @@ -268,7 +268,7 @@ jobs:
                 !./target/*/*.tar.gz
               key: never_saved
               restore-keys: |
    -            4-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
    +            5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
     
           # Don't save cache after building PRs or branches other than 'main'.
           - name: Skip save cache (PR)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.