Skip to content

Commit 14608a9

Browse files
committed
Add runtime checks to AioCb methods
Prevent immutable buffers from being used with aio_read or lio_listio with LIO_READ. AioCb.from_slice no longer needs to be unsafe.
1 parent 85d59cf commit 14608a9

File tree

2 files changed

+61
-34
lines changed

2 files changed

+61
-34
lines changed

src/sys/aio.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ pub enum AioCancelStat {
6262
#[repr(C)]
6363
pub struct AioCb<'a> {
6464
aiocb: libc::aiocb,
65+
/// Tracks whether the buffer pointed to by aiocb.aio_buf is mutable
66+
mutable: bool,
6567
phantom: PhantomData<&'a mut [u8]>
6668
}
6769

@@ -81,7 +83,7 @@ impl<'a> AioCb<'a> {
8183
a.aio_nbytes = 0;
8284
a.aio_buf = null_mut();
8385

84-
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
86+
let aiocb = AioCb { aiocb: a, mutable: false, phantom: PhantomData};
8587
aiocb
8688
}
8789

@@ -102,37 +104,39 @@ impl<'a> AioCb<'a> {
102104
let mut a = AioCb::common_init(fd, prio, sigev_notify);
103105
a.aio_offset = offs;
104106
a.aio_nbytes = buf.len() as size_t;
107+
// casting an immutable buffer to a mutable pointer looks unsafe, but
108+
// technically its only unsafe to dereference it, not to create it.
105109
a.aio_buf = buf.as_ptr() as *mut c_void;
106110
a.aio_lio_opcode = opcode as ::c_int;
107111

108-
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
112+
let aiocb = AioCb { aiocb: a, mutable: true, phantom: PhantomData};
109113
aiocb
110114
}
111115

112116
/// Like `from_mut_slice`, but works on constant slices rather than
113117
/// mutable slices.
114118
///
115-
/// This is technically unsafe, but in practice it's fine
116-
/// to use with any aio functions except `aio_read` and `lio_listio` (with
117-
/// `opcode` set to `LIO_READ`). This method is useful when writing a const
118-
/// buffer with `aio_write`, since from_mut_slice can't work with const
119+
/// An `AioCb` created this way cannot be used with `read`, and its
120+
/// `LioOpcode` cannot be set to `LIO_READ`. This method is useful when writing a
121+
/// const buffer with `aio_write`, since from_mut_slice can't work with const
119122
/// buffers.
120123
// Note: another solution to the problem of writing const buffers would be
121124
// to genericize AioCb for both &mut [u8] and &[u8] buffers. aio_read could
122125
// take the former and aio_write could take the latter. However, then
123126
// lio_listio wouldn't work, because that function needs a slice of AioCb,
124127
// and they must all be the same type. We're basically stuck with using an
125128
// unsafe function, since aio (as designed in C) is an unsafe API.
126-
pub unsafe fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8],
127-
prio: ::c_int, sigev_notify: SigevNotify,
128-
opcode: LioOpcode) -> AioCb {
129+
pub fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8],
130+
prio: ::c_int, sigev_notify: SigevNotify,
131+
opcode: LioOpcode) -> AioCb {
129132
let mut a = AioCb::common_init(fd, prio, sigev_notify);
130133
a.aio_offset = offs;
131134
a.aio_nbytes = buf.len() as size_t;
132135
a.aio_buf = buf.as_ptr() as *mut c_void;
136+
assert!(opcode != LioOpcode::LIO_READ, "Can't read into an immutable buffer");
133137
a.aio_lio_opcode = opcode as ::c_int;
134138

135-
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
139+
let aiocb = AioCb { aiocb: a, mutable: false, phantom: PhantomData};
136140
aiocb
137141
}
138142

@@ -185,13 +189,15 @@ impl<'a> AioCb<'a> {
185189

186190
/// Asynchronously reads from a file descriptor into a buffer
187191
pub fn read(&mut self) -> Result<()> {
192+
assert!(self.mutable, "Can't read into an immutable buffer");
188193
let p: *mut libc::aiocb = &mut self.aiocb;
189194
Errno::result(unsafe { libc::aio_read(p) }).map(drop)
190195
}
191196

192197
/// Retrieve return status of an asynchronous operation. Should only be called
193198
/// once for each `AioCb`, after `aio_error` indicates that it has completed.
194199
/// The result the same as for `read`, `write`, of `fsync`.
200+
// Note: this should be just `return`, but that's a reserved word
195201
pub fn aio_return(&mut self) -> Result<isize> {
196202
let p: *mut libc::aiocb = &mut self.aiocb;
197203
Errno::result(unsafe { libc::aio_return(p) })

test/sys/test_aio.rs

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,12 @@ fn test_aio_suspend() {
9292
let mut f = tempfile().unwrap();
9393
f.write(INITIAL).unwrap();
9494

95-
let mut wcb = unsafe {
96-
AioCb::from_slice( f.as_raw_fd(),
95+
let mut wcb = AioCb::from_slice( f.as_raw_fd(),
9796
2, //offset
9897
&mut WBUF,
9998
0, //priority
10099
SigevNotify::SigevNone,
101-
LioOpcode::LIO_WRITE)
102-
};
100+
LioOpcode::LIO_WRITE);
103101

104102
let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(),
105103
8, //offset
@@ -150,6 +148,21 @@ fn test_read() {
150148
assert!(rbuf == EXPECT);
151149
}
152150

151+
// Test reading into an immutable buffer. It should fail
152+
#[test]
153+
#[should_panic(expected = "Can't read into an immutable buffer")]
154+
fn test_read_immutable_buffer() {
155+
let rbuf = vec![0; 4];
156+
let f = tempfile().unwrap();
157+
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
158+
2, //offset
159+
&rbuf,
160+
0, //priority
161+
SigevNotify::SigevNone,
162+
LioOpcode::LIO_NOP);
163+
aiocb.read().unwrap();
164+
}
165+
153166
// Test a simple aio operation with no completion notification. We must poll
154167
// for completion. Unlike test_aio_read, this test uses AioCb::from_slice
155168
#[test]
@@ -161,14 +174,12 @@ fn test_write() {
161174

162175
let mut f = tempfile().unwrap();
163176
f.write(INITIAL).unwrap();
164-
let mut aiocb = unsafe {
165-
AioCb::from_slice( f.as_raw_fd(),
177+
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
166178
2, //offset
167179
&WBUF,
168180
0, //priority
169181
SigevNotify::SigevNone,
170-
LioOpcode::LIO_NOP)
171-
};
182+
LioOpcode::LIO_NOP);
172183
aiocb.write().unwrap();
173184

174185
let err = poll_aio(&mut aiocb);
@@ -206,17 +217,15 @@ fn test_write_sigev_signal() {
206217

207218
let mut f = tempfile().unwrap();
208219
f.write(INITIAL).unwrap();
209-
let mut aiocb = unsafe {
210-
AioCb::from_slice( f.as_raw_fd(),
220+
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
211221
2, //offset
212222
&WBUF,
213223
0, //priority
214224
SigevNotify::SigevSignal {
215225
signal: Signal::SIGUSR2,
216226
si_value: 0 //TODO: validate in sigfunc
217227
},
218-
LioOpcode::LIO_NOP)
219-
};
228+
LioOpcode::LIO_NOP);
220229
aiocb.write().unwrap();
221230
while unsafe { signaled == 0 } {
222231
thread::sleep(time::Duration::from_millis(10));
@@ -244,14 +253,12 @@ fn test_lio_listio_wait() {
244253
f.write(INITIAL).unwrap();
245254

246255
{
247-
let mut wcb = unsafe {
248-
AioCb::from_slice( f.as_raw_fd(),
256+
let mut wcb = AioCb::from_slice( f.as_raw_fd(),
249257
2, //offset
250258
&WBUF,
251259
0, //priority
252260
SigevNotify::SigevNone,
253-
LioOpcode::LIO_WRITE)
254-
};
261+
LioOpcode::LIO_WRITE);
255262

256263
let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(),
257264
8, //offset
@@ -288,14 +295,12 @@ fn test_lio_listio_nowait() {
288295
f.write(INITIAL).unwrap();
289296

290297
{
291-
let mut wcb = unsafe {
292-
AioCb::from_slice( f.as_raw_fd(),
298+
let mut wcb = AioCb::from_slice( f.as_raw_fd(),
293299
2, //offset
294300
&WBUF,
295301
0, //priority
296302
SigevNotify::SigevNone,
297-
LioOpcode::LIO_WRITE)
298-
};
303+
LioOpcode::LIO_WRITE);
299304

300305
let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(),
301306
8, //offset
@@ -339,14 +344,12 @@ fn test_lio_listio_signal() {
339344
f.write(INITIAL).unwrap();
340345

341346
{
342-
let mut wcb = unsafe {
343-
AioCb::from_slice( f.as_raw_fd(),
347+
let mut wcb = AioCb::from_slice( f.as_raw_fd(),
344348
2, //offset
345349
&WBUF,
346350
0, //priority
347351
SigevNotify::SigevNone,
348-
LioOpcode::LIO_WRITE)
349-
};
352+
LioOpcode::LIO_WRITE);
350353

351354
let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(),
352355
8, //offset
@@ -372,3 +375,21 @@ fn test_lio_listio_signal() {
372375
assert!(len == EXPECT.len());
373376
assert!(rbuf2 == EXPECT);
374377
}
378+
379+
// Try to use lio_listio to read into an immutable buffer. It should fail
380+
#[test]
381+
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
382+
#[should_panic(expected = "Can't read into an immutable buffer")]
383+
fn test_lio_listio_read_immutable() {
384+
let rbuf = vec![0; 4];
385+
let f = tempfile().unwrap();
386+
387+
388+
let mut rcb = AioCb::from_slice( f.as_raw_fd(),
389+
2, //offset
390+
&rbuf,
391+
0, //priority
392+
SigevNotify::SigevNone,
393+
LioOpcode::LIO_READ);
394+
let _ = lio_listio(LioMode::LIO_NOWAIT, &[&mut rcb], SigevNotify::SigevNone);
395+
}

0 commit comments

Comments
 (0)