Skip to content

Commit 11c63ea

Browse files
committed
Fix a soundness problem with get
1 parent e3fb706 commit 11c63ea

File tree

2 files changed

+79
-31
lines changed

2 files changed

+79
-31
lines changed

src/libstd/local_data.rs

+8
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,11 @@ fn test_static_pointer() {
273273
set(key, @&VALUE);
274274
}
275275
}
276+
277+
#[test]
278+
fn test_owned() {
279+
unsafe {
280+
fn key(_x: ~int) { }
281+
set(key, ~1);
282+
}
283+
}

src/libstd/task/local_data_priv.rs

+71-31
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,36 @@ impl Handle {
4848
trait LocalData {}
4949
impl<T: 'static> LocalData for T {}
5050

51-
// The task-local-map actually stores all TLS information. Right now it's a list
52-
// of triples of (key, value, loans). The key is a code pointer (right now at
53-
// least), the value is a trait so destruction can work, and the loans value
54-
// is a count of the number of times the value is currently on loan via
55-
// `local_data_get`.
51+
// The task-local-map stores all TLS information for the currently running task.
52+
// It is stored as an owned pointer into the runtime, and it's only allocated
53+
// when TLS is used for the first time. This map must be very carefully
54+
// constructed because it has many mutable loans unsoundly handed out on it to
55+
// the various invocations of TLS requests.
5656
//
57-
// TLS is designed to be able to store owned data, so `local_data_get` must
58-
// return a borrowed pointer to this data. In order to have a proper lifetime, a
59-
// borrowed pointer is instead yielded to a closure specified to the `get`
60-
// function. As a result, it would be unsound to perform `local_data_set` on the
61-
// same key inside of a `local_data_get`, so we ensure at runtime that this does
62-
// not happen.
57+
// One of the most important operations is loaning a value via `get` to a
58+
// caller. In doing so, the slot that the TLS entry is occupying cannot be
59+
// invalidated because upon returning it's loan state must be updated. Currently
60+
// the TLS map is a vector, but this is possibly dangerous because the vector
61+
// can be reallocated/moved when new values are pushed onto it.
6362
//
64-
// n.b. Has to be a pointer at outermost layer; the foreign call returns void *.
63+
// This problem currently isn't solved in a very elegant way. Inside the `get`
64+
// function, it internally "invalidates" all references after the loan is
65+
// finished and looks up into the vector again. In theory this will prevent
66+
// pointers from being moved under our feet so long as LLVM doesn't go too crazy
67+
// with the optimizations.
68+
//
69+
// n.b. Other structures are not sufficient right now:
70+
// * HashMap uses ~[T] internally (push reallocates/moves)
71+
// * TreeMap is plausible, but it's in extra
72+
// * dlist plausible, but not in std
73+
// * a custom owned linked list was attempted, but difficult to write
74+
// and involved a lot of extra code bloat
75+
//
76+
// n.b. Has to be stored with a pointer at outermost layer; the foreign call
77+
// returns void *.
6578
//
6679
// n.b. If TLS is used heavily in future, this could be made more efficient with
67-
// a proper map.
80+
// a proper map.
6881
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
6982
type TLSValue = ~LocalData:;
7083

@@ -181,32 +194,59 @@ pub unsafe fn local_pop<T: 'static>(handle: Handle,
181194
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
182195
key: local_data::Key<T>,
183196
f: &fn(Option<&T>) -> U) -> U {
184-
// This does in theory take multiple mutable loans on the tls map, but the
185-
// references returned are never removed because the map is only increasing
186-
// in size (it never shrinks).
197+
// This function must be extremely careful. Because TLS can store owned
198+
// values, and we must have some form of `get` function other than `pop`,
199+
// this function has to give a `&` reference back to the caller.
200+
//
201+
// One option is to return the reference, but this cannot be sound because
202+
// the actual lifetime of the object is not known. The slot in TLS could not
203+
// be modified until the object goes out of scope, but the TLS code cannot
204+
// know when this happens.
205+
//
206+
// For this reason, the reference is yielded to a specified closure. This
207+
// way the TLS code knows exactly what the lifetime of the yielded pointer
208+
// is, allowing callers to acquire references to owned data. This is also
209+
// sound so long as measures are taken to ensure that while a TLS slot is
210+
// loaned out to a caller, it's not modified recursively.
187211
let map = get_local_map(handle);
188212
let key_value = key_to_key_value(key);
189-
for map.mut_iter().advance |entry| {
213+
214+
let pos = map.iter().position(|entry| {
190215
match *entry {
191-
Some((k, ref data, ref mut loans)) if k == key_value => {
192-
let ret;
193-
*loans = *loans + 1;
194-
// data was created with `~~T as ~LocalData`, so we extract
195-
// pointer part of the trait, (as ~~T), and then use compiler
196-
// coercions to achieve a '&' pointer
197-
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
198-
(_vtable, ref box) => {
199-
let value: &T = **box;
200-
ret = f(Some(value));
216+
Some((k, _, _)) if k == key_value => true, _ => false
217+
}
218+
});
219+
match pos {
220+
None => { return f(None); }
221+
Some(i) => {
222+
let ret;
223+
match map[i] {
224+
Some((_, ref data, ref mut loans)) => {
225+
*loans = *loans + 1;
226+
// data was created with `~~T as ~LocalData`, so we extract
227+
// pointer part of the trait, (as ~~T), and then use
228+
// compiler coercions to achieve a '&' pointer
229+
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
230+
(_vtable, ref box) => {
231+
let value: &T = **box;
232+
ret = f(Some(value));
233+
}
201234
}
202235
}
203-
*loans = *loans - 1;
204-
return ret;
236+
_ => libc::abort()
205237
}
206-
_ => {}
238+
239+
// n.b. 'data' and 'loans' are both invalid pointers at the point
240+
// 'f' returned because `f` could have appended more TLS items which
241+
// in turn relocated the vector. Hence we do another lookup here to
242+
// fixup the loans.
243+
match map[i] {
244+
Some((_, _, ref mut loans)) => { *loans = *loans - 1; }
245+
None => { libc::abort(); }
246+
}
247+
return ret;
207248
}
208249
}
209-
return f(None);
210250
}
211251

212252
pub unsafe fn local_set<T: 'static>(handle: Handle,

0 commit comments

Comments
 (0)