Skip to content

Commit d8a1936

Browse files
detlyvberger
andauthored
Prevent dangling pointers being left in the system's polling mechanim and reduce the amount of unsafe code in the library (#76)
* Prevent dangling pointers being left in the system's polling mechanism and reduce the amount of unsafe code in the library. Previously, dropping a source without unregistering it risked leaving a dangling pointer to token memory in the underlying polling mechanism. Now, the Poller type is responsible for keeping track of what data the system's poller (eg. epoll) is still pointing to, and maintaining ownership of it. This commit also slightly reduces the amount of unsafe code related to pointer dereferences and adds some notes justifying the safety of these operations. Co-authored-by: Victor Berger <[email protected]>
1 parent 47997d4 commit d8a1936

File tree

6 files changed

+184
-98
lines changed

6 files changed

+184
-98
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ futures-util = { version = "0.3.5", optional = true, default-features = false, f
2121
futures-io = { version = "0.3.5", optional = true }
2222
slotmap = "1.0"
2323
thiserror = "1.0"
24+
vec_map = "0.8.2"
2425

2526
[dev-dependencies]
2627
futures = "0.3.5"

src/io.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ impl<'l, F: AsRawFd> Async<'l, F> {
5454
// register in the loop
5555
let dispatcher = Rc::new(RefCell::new(IoDispatcher {
5656
fd: rawfd,
57-
token: Box::new(Token::invalid()),
57+
token: None,
5858
waker: None,
5959
is_registered: false,
6060
interest: Interest::EMPTY,
6161
last_readiness: Readiness::EMPTY,
6262
}));
6363
let key = inner.sources.borrow_mut().insert(dispatcher.clone());
64-
*(dispatcher.borrow_mut().token) = Token { key, sub_id: 0 };
64+
dispatcher.borrow_mut().token = Some(Token { key, sub_id: 0 });
6565
inner.register(&dispatcher)?;
6666

6767
// Straightforward casting would require us to add the bound `Data: 'l` but we don't actually need it
@@ -173,30 +173,30 @@ trait IoLoopInner {
173173
impl<'l, Data> IoLoopInner for LoopInner<'l, Data> {
174174
fn register(&self, dispatcher: &RefCell<IoDispatcher>) -> crate::Result<()> {
175175
let disp = dispatcher.borrow();
176-
unsafe {
177-
self.poll.borrow_mut().register(
178-
disp.fd,
179-
Interest::EMPTY,
180-
Mode::OneShot,
181-
&*disp.token as *const _,
182-
)
183-
}
176+
self.poll.borrow_mut().register(
177+
disp.fd,
178+
Interest::EMPTY,
179+
Mode::OneShot,
180+
disp.token.expect("No token for IO dispatcher"),
181+
)
184182
}
185183

186184
fn reregister(&self, dispatcher: &RefCell<IoDispatcher>) -> crate::Result<()> {
187185
let disp = dispatcher.borrow();
188-
unsafe {
189-
self.poll.borrow_mut().reregister(
190-
disp.fd,
191-
disp.interest,
192-
Mode::OneShot,
193-
&*disp.token as *const _,
194-
)
195-
}
186+
self.poll.borrow_mut().reregister(
187+
disp.fd,
188+
disp.interest,
189+
Mode::OneShot,
190+
disp.token.expect("No token for IO dispatcher"),
191+
)
196192
}
197193

198194
fn kill(&self, dispatcher: &RefCell<IoDispatcher>) {
199-
let key = dispatcher.borrow().token.key;
195+
let key = dispatcher
196+
.borrow()
197+
.token
198+
.expect("No token for IO dispatcher")
199+
.key;
200200
let _source = self
201201
.sources
202202
.borrow_mut()
@@ -207,7 +207,7 @@ impl<'l, Data> IoLoopInner for LoopInner<'l, Data> {
207207

208208
struct IoDispatcher {
209209
fd: RawFd,
210-
token: Box<Token>,
210+
token: Option<Token>,
211211
waker: Option<Waker>,
212212
is_registered: bool,
213213
interest: Interest,

src/sources/generic.rs

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ pub struct Generic<F: AsRawFd, E = std::io::Error> {
5353
pub interest: Interest,
5454
/// The programmed mode
5555
pub mode: Mode,
56-
token: Box<Token>,
56+
57+
// This token is used by the event loop logic to look up this source when an
58+
// event occurs.
59+
token: Option<Token>,
5760

5861
// This allows us to make the associated error and return types generic.
5962
_error_type: PhantomData<E>,
@@ -67,7 +70,7 @@ impl<F: AsRawFd> Generic<F, std::io::Error> {
6770
file,
6871
interest,
6972
mode,
70-
token: Box::new(Token::invalid()),
73+
token: None,
7174
_error_type: PhantomData::default(),
7275
}
7376
}
@@ -78,7 +81,7 @@ impl<F: AsRawFd> Generic<F, std::io::Error> {
7881
file,
7982
interest,
8083
mode,
81-
token: Box::new(Token::invalid()),
84+
token: None,
8285
_error_type: PhantomData::default(),
8386
}
8487
}
@@ -110,23 +113,20 @@ where
110113
where
111114
C: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
112115
{
113-
if token != *self.token {
116+
// If the token is invalid or not ours, skip processing.
117+
if self.token != Some(token) {
114118
return Ok(PostAction::Continue);
115119
}
120+
116121
callback(readiness, &mut self.file)
117122
}
118123

119124
fn register(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> crate::Result<()> {
120-
let token = Box::new(token_factory.token());
121-
unsafe {
122-
poll.register(
123-
self.file.as_raw_fd(),
124-
self.interest,
125-
self.mode,
126-
&*token as *const _,
127-
)?;
128-
}
129-
self.token = token;
125+
let token = token_factory.token();
126+
127+
poll.register(self.file.as_raw_fd(), self.interest, self.mode, token)?;
128+
129+
self.token = Some(token);
130130
Ok(())
131131
}
132132

@@ -135,22 +135,17 @@ where
135135
poll: &mut Poll,
136136
token_factory: &mut TokenFactory,
137137
) -> crate::Result<()> {
138-
let token = Box::new(token_factory.token());
139-
unsafe {
140-
poll.reregister(
141-
self.file.as_raw_fd(),
142-
self.interest,
143-
self.mode,
144-
&*token as *const _,
145-
)?;
146-
}
147-
self.token = token;
138+
let token = token_factory.token();
139+
140+
poll.reregister(self.file.as_raw_fd(), self.interest, self.mode, token)?;
141+
142+
self.token = Some(token);
148143
Ok(())
149144
}
150145

151146
fn unregister(&mut self, poll: &mut Poll) -> crate::Result<()> {
152147
poll.unregister(self.file.as_raw_fd())?;
153-
self.token = Box::new(Token::invalid());
148+
self.token = None;
154149
Ok(())
155150
}
156151
}

src/sys/epoll.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,23 @@ impl Epoll {
4848
let events = buffer
4949
.iter()
5050
.take(n_ready)
51-
.map(|event| PollEvent {
52-
readiness: flags_to_readiness(event.events()),
53-
token: unsafe { *(event.data() as usize as *const Token) },
51+
.map(|event| {
52+
// In C, the underlying data type is a union including a void
53+
// pointer; in Rust's FFI bindings, it only exposes the u64. The
54+
// round-trip conversion is valid however.
55+
let token_ptr = event.data() as usize as *const Token;
56+
PollEvent {
57+
readiness: flags_to_readiness(event.events()),
58+
// Why this is safe: it points to memory boxed and owned by
59+
// the parent Poller type.
60+
token: unsafe { *token_ptr },
61+
}
5462
})
5563
.collect();
5664
Ok(events)
5765
}
5866

59-
pub unsafe fn register(
67+
pub fn register(
6068
&mut self,
6169
fd: RawFd,
6270
interest: Interest,
@@ -68,7 +76,7 @@ impl Epoll {
6876
.map_err(Into::into)
6977
}
7078

71-
pub unsafe fn reregister(
79+
pub fn reregister(
7280
&mut self,
7381
fd: RawFd,
7482
interest: Interest,

src/sys/kqueue.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,27 @@ impl Kqueue {
4343
let ret = buffer
4444
.iter()
4545
.take(nevents)
46-
.map(|event| PollEvent {
47-
readiness: Readiness {
48-
readable: event.filter() == Ok(EventFilter::EVFILT_READ),
49-
writable: event.filter() == Ok(EventFilter::EVFILT_WRITE),
50-
error: event.flags().contains(EventFlag::EV_ERROR) && event.data() != 0,
51-
},
52-
token: unsafe { *(event.udata() as usize as *const Token) },
46+
.map(|event| {
47+
// The kevent data field in Rust's libc FFI bindings is an
48+
// intptr_t, which is specified to allow this kind of
49+
// conversion.
50+
let token_ptr = event.udata() as usize as *const Token;
51+
PollEvent {
52+
readiness: Readiness {
53+
readable: event.filter() == Ok(EventFilter::EVFILT_READ),
54+
writable: event.filter() == Ok(EventFilter::EVFILT_WRITE),
55+
error: event.flags().contains(EventFlag::EV_ERROR) && event.data() != 0,
56+
},
57+
// Why this is safe: it points to memory boxed and owned by
58+
// the parent Poller type.
59+
token: unsafe { *token_ptr },
60+
}
5361
})
5462
.collect();
5563
Ok(ret)
5664
}
5765

58-
pub unsafe fn register(
66+
pub fn register(
5967
&mut self,
6068
fd: RawFd,
6169
interest: Interest,
@@ -65,7 +73,7 @@ impl Kqueue {
6573
self.reregister(fd, interest, mode, token)
6674
}
6775

68-
pub unsafe fn reregister(
76+
pub fn reregister(
6977
&mut self,
7078
fd: RawFd,
7179
interest: Interest,

0 commit comments

Comments
 (0)