Skip to content

Commit c3de71d

Browse files
committed
Fix critical security vulnerability in automigration code
1 parent c6220cb commit c3de71d

File tree

3 files changed

+57
-17
lines changed

3 files changed

+57
-17
lines changed

crates/schema/src/auto_migrate.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub struct AutoMigratePlan<'def> {
4343
/// There is also an implied check: that the schema in the database is compatible with the old ModuleDef.
4444
pub prechecks: Vec<AutoMigratePrecheck<'def>>,
4545
/// The migration steps to perform.
46-
/// Order should not matter, as the steps are independent.
46+
/// Order matters: `Remove`s of a particular `Def` must be ordered before `Add`s.
4747
pub steps: Vec<AutoMigrateStep<'def>>,
4848
}
4949

@@ -59,29 +59,46 @@ pub enum AutoMigratePrecheck<'def> {
5959
/// A step in an automatic migration.
6060
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord)]
6161
pub enum AutoMigrateStep<'def> {
62+
// It is important FOR CORRECTNESS that `Remove` variants are declared before `Add` variants in this enum!
63+
//
64+
// The ordering is used to sort the steps of an auto-migration.
65+
// If adds go before removes, the following can occur:
66+
//
67+
// 1. `AddIndex("my_special_boy)`
68+
// 2. `RemoveIndex("my_special_boy)`
69+
//
70+
// You see the problem.
71+
//
72+
// For now, we just ensure that we declare all `Remove` variants before `Add` variants
73+
// and let `#[derive(PartialOrd)]` take care of the rest.
74+
// TODO: when this enum is made serializable, a more durable fix will be needed here.
75+
// Probably we will want to have separate arrays of add and remove steps.
76+
//
77+
/// Remove an index.
78+
RemoveIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
79+
/// Remove a constraint.
80+
RemoveConstraint(<ConstraintDef as ModuleDefLookup>::Key<'def>),
81+
/// Remove a sequence.
82+
RemoveSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
83+
/// Remove a schedule annotation from a table.
84+
RemoveSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
85+
/// Remove a row-level security query.
86+
RemoveRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
87+
6288
/// Add a table, including all indexes, constraints, and sequences.
6389
/// There will NOT be separate steps in the plan for adding indexes, constraints, and sequences.
6490
AddTable(<TableDef as ModuleDefLookup>::Key<'def>),
6591
/// Add an index.
6692
AddIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
67-
/// Remove an index.
68-
RemoveIndex(<IndexDef as ModuleDefLookup>::Key<'def>),
69-
/// Remove a constraint.
70-
RemoveConstraint(<ConstraintDef as ModuleDefLookup>::Key<'def>),
7193
/// Add a sequence.
7294
AddSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
73-
/// Remove a sequence.
74-
RemoveSequence(<SequenceDef as ModuleDefLookup>::Key<'def>),
75-
/// Change the access of a table.
76-
ChangeAccess(<TableDef as ModuleDefLookup>::Key<'def>),
7795
/// Add a schedule annotation to a table.
7896
AddSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
79-
/// Remove a schedule annotation from a table.
80-
RemoveSchedule(<ScheduleDef as ModuleDefLookup>::Key<'def>),
8197
/// Add a row-level security query.
8298
AddRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
83-
/// Remove a row-level security query.
84-
RemoveRowLevelSecurity(<RawRowLevelSecurityDefV9 as ModuleDefLookup>::Key<'def>),
99+
100+
/// Change the access of a table.
101+
ChangeAccess(<TableDef as ModuleDefLookup>::Key<'def>),
85102
}
86103

87104
/// Something that might prevent an automatic migration.
@@ -412,9 +429,14 @@ fn auto_migrate_constraints(plan: &mut AutoMigratePlan, new_tables: &HashSet<&Id
412429
.collect_all_errors()
413430
}
414431

415-
// Because we can refer to many tables and fields on the row level-security query, we need to remove all of them,
416-
// then add the new ones, instead of trying to track the graph of dependencies.
417432
fn auto_migrate_row_level_security(plan: &mut AutoMigratePlan) -> Result<()> {
433+
// Because we can refer to many tables and fields on the row level-security query, we need to remove all of them,
434+
// then add the new ones, instead of trying to track the graph of dependencies.
435+
// When pretty-printing, steps to remove and re-add a row-level-security policy are not shown,
436+
// since they are an implementation detail that will be surprising to users.
437+
// TODO: change this to not add-and-remove unchanged policies, and hand the responsibility for reinitializing
438+
// queries to the `core` crate instead.
439+
// When you do this, you will need to update `pretty_print.rs`.
418440
for rls in plan.old.row_level_security() {
419441
plan.steps.push(AutoMigrateStep::RemoveRowLevelSecurity(rls.key()));
420442
}

crates/schema/src/auto_migrate/pretty_print.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use colored::{self, Colorize};
66
use lazy_static::lazy_static;
77
use regex::Regex;
88
use spacetimedb_lib::{
9-
db::raw_def::v9::{TableAccess, TableType},
9+
db::raw_def::v9::{RawRowLevelSecurityDefV9, TableAccess, TableType},
1010
AlgebraicType,
1111
};
1212
use spacetimedb_primitives::{ColId, ColList};
@@ -188,8 +188,9 @@ pub fn pretty_print(plan: &MigratePlan) -> Result<String, fmt::Error> {
188188

189189
write!(
190190
outr,
191-
"- {} auto-increment constraint on column {} of table {}",
191+
"- {} auto-increment constraint {} on column {} of table {}",
192192
removed,
193+
constraint_name(*sequence),
193194
column_name_from_id(table_def, sequence_def.column),
194195
table_name(&*table_def.name),
195196
)?;
@@ -232,10 +233,26 @@ pub fn pretty_print(plan: &MigratePlan) -> Result<String, fmt::Error> {
232233
)?;
233234
}
234235
AutoMigrateStep::AddRowLevelSecurity(rls) => {
236+
// Implementation detail: Row-level-security policies are always removed and re-added
237+
// because the `core` crate needs to recompile some stuff.
238+
// We hide this from the user.
239+
if plan.old.lookup::<RawRowLevelSecurityDefV9>(*rls)
240+
== plan.new.lookup::<RawRowLevelSecurityDefV9>(*rls)
241+
{
242+
continue;
243+
}
235244
writeln!(outr, "- {} row level security policy:", created)?;
236245
writeln!(outr, " `{}`", rls.blue())?;
237246
}
238247
AutoMigrateStep::RemoveRowLevelSecurity(rls) => {
248+
// Implementation detail: Row-level-security policies are always removed and re-added
249+
// because the `core` crate needs to recompile some stuff.
250+
// We hide this from the user.
251+
if plan.old.lookup::<RawRowLevelSecurityDefV9>(*rls)
252+
== plan.new.lookup::<RawRowLevelSecurityDefV9>(*rls)
253+
{
254+
continue;
255+
}
239256
writeln!(outr, "- {} row level security policy:", removed)?;
240257
writeln!(outr, " `{}`", rls.blue())?;
241258
}

crates/schema/src/def.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub struct ModuleDef {
122122
/// The row-level security policies.
123123
///
124124
/// **Note**: Are only validated syntax-wise.
125+
// TODO: for consistency, this should be changed to store a `RowLevelSecurityDef` instead.
125126
row_level_security_raw: HashMap<RawSql, RawRowLevelSecurityDefV9>,
126127
}
127128

0 commit comments

Comments
 (0)