Merge 'Disable extension loading at runtime' from Preston Thorpe

SQLite disables the `load_extension` function by default in the core
library.
There are a couple reasons we should do the same.
1. VTab extensions need up to update the schema, and if there are
multiple connections we can/will corrupt the schema or read garbage
memory if an extension is loaded while another connection changes the
schema.
2. for _general security reasons_ ™️, which is why sqlite disables
this by default.
This PR leaves it enabled in the CLI, where there can be only a single
connection due to the lack of multi-process support right now.

Closes #3250
This commit is contained in:
Pekka Enberg
2025-09-23 07:28:44 +03:00
committed by GitHub
6 changed files with 20 additions and 2 deletions

View File

@@ -28,7 +28,7 @@ ctrlc = "3.4.4"
dirs = "5.0.1"
env_logger = { workspace = true }
libc = "0.2.172"
turso_core = { path = "../core", default-features = true, features = [] }
turso_core = { path = "../core", default-features = true, features = ["cli_only"] }
limbo_completion = { path = "../extensions/completion", features = ["static"] }
miette = { workspace = true, features = ["fancy"] }
nu-ansi-term = {version = "0.50.1", features = ["serde", "derive_serde_style"]}

View File

@@ -184,7 +184,8 @@ impl Limbo {
.with_mvcc(opts.experimental_mvcc)
.with_indexes(indexes_enabled)
.with_views(opts.experimental_views)
.with_strict(opts.experimental_strict),
.with_strict(opts.experimental_strict)
.turso_cli(),
None,
)?;
let conn = db.connect()?;

View File

@@ -30,6 +30,7 @@ serde = ["dep:serde"]
series = []
encryption = []
checksum = []
cli_only = []
[target.'cfg(target_os = "linux")'.dependencies]
io-uring = { version = "0.7.5", optional = true }

View File

@@ -316,6 +316,7 @@ mod tests {
enable_indexes: false,
enable_views: true,
enable_strict: false,
enable_load_extension: false,
},
None,
)?;

View File

@@ -106,6 +106,7 @@ pub struct DatabaseOpts {
pub enable_indexes: bool,
pub enable_views: bool,
pub enable_strict: bool,
enable_load_extension: bool,
}
impl Default for DatabaseOpts {
@@ -115,6 +116,7 @@ impl Default for DatabaseOpts {
enable_indexes: true,
enable_views: false,
enable_strict: false,
enable_load_extension: false,
}
}
}
@@ -124,6 +126,12 @@ impl DatabaseOpts {
Self::default()
}
#[cfg(feature = "cli_only")]
pub fn turso_cli(mut self) -> Self {
self.enable_load_extension = true;
self
}
pub fn with_mvcc(mut self, enable: bool) -> Self {
self.enable_mvcc = enable;
self
@@ -755,6 +763,10 @@ impl Database {
Ok((io, db))
}
pub(crate) fn can_load_extensions(&self) -> bool {
self.opts.enable_load_extension
}
#[inline]
pub(crate) fn with_schema_mut<T>(&self, f: impl FnOnce(&mut Schema) -> Result<T>) -> Result<T> {
let mut schema_ref = self.schema.lock().map_err(|_| LimboError::SchemaLocked)?;

View File

@@ -4781,6 +4781,9 @@ pub fn op_function(
#[cfg(feature = "fs")]
#[cfg(not(target_family = "wasm"))]
ScalarFunc::LoadExtension => {
if !program.connection.db.can_load_extensions() {
crate::bail_parse_error!("runtime extension loading is disabled");
}
let extension = &state.registers[*start_reg];
let ext = resolve_ext_path(&extension.get_value().to_string())?;
program.connection.load_extension(ext)?;