Skip to content

Commit

Permalink
fix(ext/node): enforce -RW perms on node:sqlite (#27928)
Browse files Browse the repository at this point in the history
require RW permission on the database path except when using in-memory
mode.
  • Loading branch information
littledivy authored Feb 2, 2025
1 parent 540fe7d commit 3c56e6c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 1 deletion.
21 changes: 20 additions & 1 deletion ext/node/ops/sqlite/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::rc::Rc;

use deno_core::op2;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_permissions::PermissionsContainer;
use serde::Deserialize;

use super::SqliteError;
Expand Down Expand Up @@ -41,6 +43,19 @@ pub struct DatabaseSync {

impl GarbageCollected for DatabaseSync {}

fn check_perms(state: &mut OpState, location: &str) -> Result<(), SqliteError> {
if location != ":memory:" {
state
.borrow::<PermissionsContainer>()
.check_read_with_api_name(location, Some("node:sqlite"))?;
state
.borrow::<PermissionsContainer>()
.check_write_with_api_name(location, Some("node:sqlite"))?;
}

Ok(())
}

// Represents a single connection to a SQLite database.
#[op2]
impl DatabaseSync {
Expand All @@ -53,12 +68,15 @@ impl DatabaseSync {
#[constructor]
#[cppgc]
fn new(
state: &mut OpState,
#[string] location: String,
#[serde] options: Option<DatabaseSyncOptions>,
) -> Result<DatabaseSync, SqliteError> {
let options = options.unwrap_or_default();

let db = if options.open {
check_perms(state, &location)?;

let db = rusqlite::Connection::open(&location)?;
if options.enable_foreign_key_constraints {
db.execute("PRAGMA foreign_keys = ON", [])?;
Expand All @@ -81,11 +99,12 @@ impl DatabaseSync {
// via the constructor. An exception is thrown if the database is
// already opened.
#[fast]
fn open(&self) -> Result<(), SqliteError> {
fn open(&self, state: &mut OpState) -> Result<(), SqliteError> {
if self.conn.borrow().is_some() {
return Err(SqliteError::AlreadyOpen);
}

check_perms(state, &self.location)?;
let db = rusqlite::Connection::open(&self.location)?;
if self.options.enable_foreign_key_constraints {
db.execute("PRAGMA foreign_keys = ON", [])?;
Expand Down
4 changes: 4 additions & 0 deletions ext/node/ops/sqlite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ mod database;
mod statement;

pub use database::DatabaseSync;
use deno_permissions::PermissionCheckError;
pub use statement::StatementSync;

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum SqliteError {
#[class(inherit)]
#[error(transparent)]
Permission(#[from] PermissionCheckError),
#[class(generic)]
#[error(transparent)]
SqliteError(#[from] rusqlite::Error),
Expand Down
10 changes: 10 additions & 0 deletions tests/unit_node/sqlite_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,13 @@ Deno.test("[node/sqlite] StatementSync blob are Uint8Array", () => {
const row = obj[0] as Record<string, Uint8Array>;
assert(row["cast('test' as blob)"] instanceof Uint8Array);
});

Deno.test({
name: "[node/sqlite] sqlite permissions",
permissions: { read: false, write: false },
fn() {
assertThrows(() => {
new DatabaseSync("test.db");
}, Deno.errors.NotCapable);
},
});

0 comments on commit 3c56e6c

Please sign in to comment.