-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix a use-after-free bug in our Sqlite backend #2663
Conversation
7c644d2
to
dcc7e62
Compare
stmt.field_name(i).map(|column| { | ||
let column = column | ||
.to_str() | ||
dbg!(i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dbg
let column = column | ||
.to_str() | ||
dbg!(i); | ||
dbg!(self.stmt.field_name(i)).map(|column| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dbg
.to_str() | ||
dbg!(i); | ||
dbg!(self.stmt.field_name(i)).map(|column| { | ||
let column = dbg!(column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dbg
/// > reprepared by the first call to sqlite3_step() for a particular | ||
/// > run or until the next call to sqlite3_column_name() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what "for a particular run" means in this context. I'd be able to review more usefully if I understood precisely what this means :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the exact wording of the SQLite documentation for this function. I interpret this in such a way that the pointer for a given column name can change if you execute a prepared statement and after the executing call sqlite3_step()
the first time for this newly executed statement. (sqlite3_step()
is basically Iterator::next()
to get a new row from the result set). So we should fine as long as we call sqlite3_column_name()
after the first call to sqlite3_step()
for a particular run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm so if I understand correctly "for a particular run" means for us "for an instance of StatementUse
".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats correct 👍
diesel/src/sqlite/connection/stmt.rs
Outdated
/// > run or until the next call to sqlite3_column_name() | ||
/// > or sqlite3_column_name16() on the same column. | ||
/// | ||
/// https://www.sqlite.org/c3ref/column_name.html | ||
unsafe fn field_name<'a>(&self, idx: usize) -> Option<&'a CStr> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc states that "The lifetime of the returned CStr
is shorter than self.", but it looks like this signature actually allows extending the lifetime.
fn field_name(&self, idx: usize) -> Option<&CStr>;
or (if we want to be more explicit)
fn field_name<'a, 'b: 'a>(&'a self, idx: usize) -> Option<&'b CStr>;
would solve part of the issue.
row.map(|row| { | ||
let row = row.into_named(&self.column_indices); | ||
let row = row.into_named(self.column_indices.as_ref().expect("it's there because we populated it above")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this seems to not break memory safety specifically because this call to into_named
enforces downgrading the lifetime of 'a
in HashMap<&'a str, usize>
to the lifetime of self
.
Maybe we could explicit it there in order to contain why this works closer? (It looks that currently it wouldn't be hard to change the signature of into_named
, see it compile and call it a day.)
impl<'a, T> Iterator for NamedStatementIterator<'a, T>
where
T: QueryableByName<Sqlite>,
{
type Item = QueryResult<T>;
fn next<'s>(&'s mut self) -> Option<Self::Item> {
let row = match self.stmt.step() {
Ok(row) => row,
Err(e) => return Some(Err(e)),
};
if self.column_indices.is_none() {
if let Err(e) = self.populate_column_indices() {
return Some(Err(e));
}
}
row.map(|row| {
// explicitly downgrade the lifetime to a correct one
let column_indices: &'s HashMap<&'s str, usize> = self.column_indices.as_ref().expect("it's there because we populated it above");
let row = row.into_named(column_indices);
T::build(&row).map_err(DeserializationError)
})
}
}
Additionally it looks like we could add a doc comment on column_indices
of to specify that 'a
is not precisely correct in that context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not possible, it results in the following error message:
error[E0597]: `self` does not live long enough
--> diesel/src/sqlite/connection/statement_iterator.rs:94:34
|
83 | fn next<'s>(&'s mut self) -> Option<Self::Item> {
| -- lifetime `'s` defined here
...
93 | row.map(|row| {
| ----- value captured here
94 | let indices: &'s _ = self.column_indices.as_ref().expect("it's there because we populated it above");
| ----- ^^^^ borrowed value does not live long enough
| |
| type annotation requires that `self` is borrowed for `'s`
...
98 | }
| - `self` dropped here while still borrowed
That written: I'm quite sure no one will actually touch this code again, as this is already rewritten on the master branch in such a way that this cannot happen there. (Though we should check if we could trigger there the multiple calls to sqlite3_column_name
thing there.)
We've missused `sqlite3_column_name`. The [SQLite](https://www.sqlite.org/c3ref/column_name.html) documentation states that the following: > The returned string pointer is valid until either the prepared statement > is destroyed by sqlite3_finalize() or until the statement is automatically > reprepared by the first call to sqlite3_step() for a particular > run or until the next call to sqlite3_column_name() > or sqlite3_column_name16() on the same column. As part of our `query_by_name` infrastructure we've first received all field names for the prepared statement and stored them as string slices for later use. After that we called `sqlite3_step()` for the first time, which invalids the pointer and therefore the stored string slice. I've opted to fix this by just populating the field name map after the first call to `sqlite3_step()` as a minimal fix for this issue for the 1.x release series. We need to investigate further if and how a similar issue can occur with the current master branch. The corresponding code changed quite a lot and at least this particular behaviour is not possible anymore.
dcc7e62
to
2a612f0
Compare
51da43c
to
cead1a3
Compare
This is a full fix for diesel-rs#2663. > The returned string pointer is valid until either the prepared statement > is destroyed by sqlite3_finalize() or until the statement is automatically > reprepared by the first call to sqlite3_step() for a particular > run or until the next call to sqlite3_column_name() > or sqlite3_column_name16() on the same column. As concequence this means we cannot just call `sqlite3_column_name` in `Field::field_name()`. To fix this issue I decided to get all column names pointers once after calling `step()` the first time and cache them as part of the specific Statement instance. This introduces a lot of lifetimes to be very explicit about what is valid for which time.
3539: Bump diesel from 1.4.5 to 1.4.6 r=mergify[bot] a=dependabot[bot] Bumps [diesel](https://github.com/diesel-rs/diesel) from 1.4.5 to 1.4.6. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/diesel-rs/diesel/releases">diesel's releases</a>.</em></p> <blockquote> <h2>1.4.6</h2> <h3>Fixed</h3> <ul> <li>Fixed a use-after-free issue in the <code>QueryableByName</code> implementation of our <code>Sqlite</code> backend</li> <li>Updated several dependencies</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/diesel-rs/diesel/blob/v1.4.6/CHANGELOG.md">diesel's changelog</a>.</em></p> <blockquote> <h2>[1.4.6] - 2021-03-05</h2> <h3>Fixed</h3> <ul> <li>Fixed a use-after-free issue in the <code>QueryableByName</code> implementation of our <code>Sqlite</code> backend</li> <li>Updated several dependencies</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/diesel-rs/diesel/commit/b0c8e99b3e16a81a99c23af1ea90963d2ec7477d"><code>b0c8e99</code></a> Release diesel 1.4.6</li> <li><a href="https://github.com/diesel-rs/diesel/commit/ae72835078156a3ff3b5b17e2a235189ef892af7"><code>ae72835</code></a> Merge pull request <a href="https://github.com/diesel-rs/diesel/issues/2663">#2663</a> from weiznich/bugfix/fix_use_after_free</li> <li><a href="https://github.com/diesel-rs/diesel/commit/cead1a3b5d14ec90d356306b168ba0940167d36b"><code>cead1a3</code></a> Downgrade max bigdecimal version as the new version is incompatible</li> <li><a href="https://github.com/diesel-rs/diesel/commit/2a612f078c625fa4c63012ec1f56e68141720229"><code>2a612f0</code></a> Run cargo fmt</li> <li><a href="https://github.com/diesel-rs/diesel/commit/d699f1499f3cc1c6920cb6aebc72a3aff1880c8f"><code>d699f14</code></a> Bump some dependencies</li> <li><a href="https://github.com/diesel-rs/diesel/commit/d2192118eba79ff08044455eabc784525de8d854"><code>d219211</code></a> Address review comments</li> <li><a href="https://github.com/diesel-rs/diesel/commit/d4133288261612d43853245aa886e1a3a0d2dbfe"><code>d413328</code></a> Fix a use-after-free bug in our Sqlite backend</li> <li><a href="https://github.com/diesel-rs/diesel/commit/8e9e639983b78fce456fd5cf5ea8aa1f0556ff66"><code>8e9e639</code></a> More fixes for mysql setup</li> <li><a href="https://github.com/diesel-rs/diesel/commit/453cfb8eadbc39d26bbad928e3889864d4e9a707"><code>453cfb8</code></a> Remove clippy from ci on release branch</li> <li><a href="https://github.com/diesel-rs/diesel/commit/d856d35fd5f134d3405e1f1324419ed3612fe0d1"><code>d856d35</code></a> Fix mysql instalation on macos</li> <li>Additional commits viewable in <a href="https://github.com/diesel-rs/diesel/compare/v1.4.5...v1.4.6">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=diesel&package-manager=cargo&previous-version=1.4.5&new-version=1.4.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually </details> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
See also: https://rustsec.org/advisories/RUSTSEC-2021-0037.html diesel-rs/diesel#2663 Signed-off-by: Matthias Beyer <[email protected]>
See also: https://rustsec.org/advisories/RUSTSEC-2021-0037.html diesel-rs/diesel#2663 Signed-off-by: Matthias Beyer <[email protected]> (cherry picked from commit 38b6ded)
We've missused
sqlite3_column_name
. TheSQLite documentation
states that the following:
As part of our
query_by_name
infrastructure we've first received allfield names for the prepared statement and stored them as string slices
for later use. After that we called
sqlite3_step()
for the first time,which invalids the pointer and therefore the stored string slice.
I've opted to fix this by just populating the field name map after the
first call to
sqlite3_step()
as a minimal fix for this issue for the1.x release series. We need to investigate further if and how a similar
issue can occur with the current master branch. The corresponding code
changed quite a lot and at least this particular behaviour is not
possible anymore.
cc @diesel-rs/contributors and @diesel-rs/core: I've plan to release this change tomorrow. Additionally I see if we need to get a RUSTSEC advisory for this or not.
Fixes #2662