jonhoo / flurry

A port of Java's ConcurrentHashMap to Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

It is unsound to accept arbitrary user-constructed Guards

jonhoo opened this issue · comments

As @Amanieu points out in #44 (comment), the current API which lets users supply their own Guard references to our methods is unsound. Specifically, a user can do the following:

use crossbeam_epoch::*;
let map = HashMap::default();
map.insert(42, String::from("hello"), &epoch::pin());

let evil = crossbeam_epoch::Collector::new();
let evil = evil.register();
let oops = map.get(&42, &evil.guard());

map.remove(&42, &epoch::pin());
// at this point, the default collector is allowed to free `"hello"`
// since no-one has the global epoch pinned as far as it is aware.
// `oops` is tied to the lifetime of a Guard that is not a part of
// the same epoch group, and so can now be dangling.
// but we can still access it!
assert_eq!(oops, "hello");

Here is a sketch of a possible solution, largely inspired by @Amanieu's proposal + #45.

First, we add a Collector field to HashMap, and add a constructor that takes a Collector (this can be no_std). The default() and new() constructors use epoch::default_collector().clone() as their Collector. Then, we add a check to every method that takes a &Guard (which is essentially all of them at the moment) that checks that the given Guard's collector matches self.collector (see SkipList::check_guard). This should take care of the safety issues.

We then add a wrapper type that is tied to a single Guard and exposes all the methods (and some more convenience ones like Index) without a Guard argument. This is #45. It should also have two constructors. One that takes a user-supplied Guard (and checks it), and one that does not (and uses epoch::pin). There should be a method to extract the Guard again from the HashMapRef (since it needs to take ownership of it to also have a variant that uses epoch::pin).

If we use a Cow-like enum, we can still just borrow a user's guard. (Not std::borrow::Cow directly since Guard can't "copy" with ToOwned.)