Fix UB caused by interactions with null streams

This commit is contained in:
Emilie Burgun
2025-01-31 17:26:40 +01:00
parent 079a69396a
commit fdc35f8b40
5 changed files with 136 additions and 53 deletions

View File

@@ -535,7 +535,7 @@ impl Machine {
/// Consults a module into the [`Machine`] from a string. /// Consults a module into the [`Machine`] from a string.
pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) { pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) {
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena); let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
self.machine_st.registers[1] = stream_as_cell!(stream); self.machine_st.registers[1] = stream.into();
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with( self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
&self.machine_st.atom_tbl, &self.machine_st.atom_tbl,
module_name module_name

View File

@@ -291,7 +291,7 @@ impl Machine {
} }
fn load_file(&mut self, path: &str, stream: Stream) { fn load_file(&mut self, path: &str, stream: Stream) {
self.machine_st.registers[1] = stream_as_cell!(stream); self.machine_st.registers[1] = stream.into();
self.machine_st.registers[2] = self.machine_st.registers[2] =
atom_as_cell!(AtomTable::build_with(&self.machine_st.atom_tbl, path)); atom_as_cell!(AtomTable::build_with(&self.machine_st.atom_tbl, path));

View File

@@ -982,6 +982,20 @@ fn cursor_position<T>(
} }
} }
impl From<Stream> for HeapCellValue {
#[inline(always)]
fn from(stream: Stream) -> Self {
if stream.is_null_stream() {
let res = atom!("null_stream");
atom_as_cell!(res)
} else {
let res = stream.as_ptr();
debug_assert!(!res.is_null());
raw_ptr_as_cell!(res)
}
}
}
impl Stream { impl Stream {
#[inline] #[inline]
pub(crate) fn position(&mut self) -> Option<(u64, usize)> { pub(crate) fn position(&mut self) -> Option<(u64, usize)> {
@@ -1626,7 +1640,7 @@ impl MachineState {
match_untyped_arena_ptr!(ptr, match_untyped_arena_ptr!(ptr,
(ArenaHeaderTag::Stream, stream) => { (ArenaHeaderTag::Stream, stream) => {
return if stream.is_null_stream() { return if stream.is_null_stream() {
Err(self.open_permission_error(stream_as_cell!(stream), caller, arity)) Err(self.open_permission_error(HeapCellValue::from(stream), caller, arity))
} else { } else {
Ok(stream) Ok(stream)
}; };
@@ -1689,7 +1703,7 @@ impl MachineState {
if let Some(alias) = stream.options().get_alias() { if let Some(alias) = stream.options().get_alias() {
atom_as_cell!(alias) atom_as_cell!(alias)
} else { } else {
stream_as_cell!(stream) stream.into()
}, },
); );
@@ -1893,3 +1907,81 @@ impl MachineState {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
use crate::machine::config::*;
#[test]
#[cfg_attr(miri, ignore)]
fn current_input_null_stream() {
let mut machine = MachineBuilder::new()
.with_streams(StreamConfig::in_memory())
.build();
let results = machine.run_query("current_input(S).").collect::<Vec<_>>();
assert_eq!(results.len(), 1);
assert!(results[0].is_ok());
}
#[test]
#[cfg_attr(miri, ignore)]
fn read_null_stream() {
let mut machine = MachineBuilder::new()
.with_streams(StreamConfig::in_memory())
.build();
let results = machine.run_query("get_code(C).").collect::<Vec<_>>();
assert_eq!(results.len(), 1);
assert!(results[0].is_err());
}
#[test]
#[cfg_attr(miri, ignore)]
fn current_output_null_stream() {
// TODO: switch to a proper solution for configuring the machine with null streams
// once `StreamConfig` supports it.
let mut machine = MachineBuilder::new().build();
machine.user_output = Stream::Null(StreamOptions::default());
machine.configure_streams();
let results = machine.run_query("current_output(S).").collect::<Vec<_>>();
assert_eq!(results.len(), 1);
assert!(results[0].is_ok());
}
#[test]
#[cfg_attr(miri, ignore)]
fn write_null_stream() {
// TODO: switch to a proper solution for configuring the machine with null streams
// once `StreamConfig` supports it.
let mut machine = MachineBuilder::new().build();
machine.user_output = Stream::Null(StreamOptions::default());
machine.configure_streams();
let results = machine.run_query("write(hello).").collect::<Vec<_>>();
assert_eq!(results.len(), 1);
assert!(results[0].is_err());
}
/// A variant of the [`write_null_stream`] that tries to write to a (null) input stream.
#[test]
#[cfg_attr(miri, ignore)]
fn write_null_input_stream() {
let mut machine = MachineBuilder::new()
.with_streams(StreamConfig::in_memory())
.build();
let results = machine
.run_query("current_input(Stream), write(Stream, hello).")
.collect::<Vec<_>>();
assert_eq!(results.len(), 1);
assert!(results[0].is_err());
}
}

View File

@@ -1881,7 +1881,7 @@ impl Machine {
let stream = self.user_input; let stream = self.user_input;
if let Some(var) = addr.as_var() { if let Some(var) = addr.as_var() {
self.machine_st.bind(var, stream_as_cell!(stream)); self.machine_st.bind(var, stream.into());
return Ok(()); return Ok(());
} }
@@ -1916,7 +1916,7 @@ impl Machine {
let stream = self.user_output; let stream = self.user_output;
if let Some(var) = addr.as_var() { if let Some(var) = addr.as_var() {
self.machine_st.bind(var, stream_as_cell!(stream)); self.machine_st.bind(var, stream.into());
return Ok(()); return Ok(());
} }
@@ -3273,7 +3273,7 @@ impl Machine {
match stream.write_all(&bytes) { match stream.write_all(&bytes) {
Ok(_) => {} Ok(_) => {}
_ => { _ => {
let addr = stream_as_cell!(stream); let addr = stream.into();
let err = self let err = self
.machine_st .machine_st
.existence_error(ExistenceError::Stream(addr)); .existence_error(ExistenceError::Stream(addr));
@@ -3323,7 +3323,7 @@ impl Machine {
_ => { _ => {
let err = self let err = self
.machine_st .machine_st
.existence_error(ExistenceError::Stream(stream_as_cell!(stream))); .existence_error(ExistenceError::Stream(stream.into()));
return Err(self.machine_st.error_form(err, stub_gen())); return Err(self.machine_st.error_form(err, stub_gen()));
} }
@@ -3336,9 +3336,9 @@ impl Machine {
return Ok(()); return Ok(());
} }
_ => { _ => {
let err = self.machine_st.existence_error(ExistenceError::Stream( let err = self
stream_as_cell!(stream), .machine_st
)); .existence_error(ExistenceError::Stream(stream.into()));
return Err(self.machine_st.error_form(err, stub_gen())); return Err(self.machine_st.error_form(err, stub_gen()));
} }
@@ -3730,7 +3730,7 @@ impl Machine {
self.indices.streams = self.indices.streams.sub(&null_streams); self.indices.streams = self.indices.streams.sub(&null_streams);
if let Some(first_stream) = first_stream { if let Some(first_stream) = first_stream {
let stream = stream_as_cell!(first_stream); let stream = first_stream.into();
let var = self.deref_register(1).as_var().unwrap(); let var = self.deref_register(1).as_var().unwrap();
@@ -3761,8 +3761,7 @@ impl Machine {
if let Some(next_stream) = next_stream { if let Some(next_stream) = next_stream {
let var = self.deref_register(2).as_var().unwrap(); let var = self.deref_register(2).as_var().unwrap();
let next_stream = stream_as_cell!(next_stream); self.machine_st.bind(var, next_stream.into());
self.machine_st.bind(var, next_stream);
} else { } else {
self.machine_st.fail = true; self.machine_st.fail = true;
} }
@@ -3779,7 +3778,7 @@ impl Machine {
if !stream.is_output_stream() { if !stream.is_output_stream() {
let stub = functor_stub(atom!("flush_output"), 1); let stub = functor_stub(atom!("flush_output"), 1);
let addr = stream_as_cell!(stream); let addr = HeapCellValue::from(stream);
let err = let err =
self.machine_st self.machine_st
@@ -3899,7 +3898,7 @@ impl Machine {
if close_result.is_err() { if close_result.is_err() {
let stub = functor_stub(atom!("close"), 1); let stub = functor_stub(atom!("close"), 1);
let addr = stream_as_cell!(stream); let addr = stream.into();
let err = self let err = self
.machine_st .machine_st
.existence_error(ExistenceError::Stream(addr)); .existence_error(ExistenceError::Stream(addr));
@@ -4472,10 +4471,9 @@ impl Machine {
self.indices.streams.insert(stream); self.indices.streams.insert(stream);
let stream = stream_as_cell!(stream);
let stream_addr = self.deref_register(2); let stream_addr = self.deref_register(2);
self.machine_st.bind(stream_addr.as_var().unwrap(), stream); self.machine_st
.bind(stream_addr.as_var().unwrap(), stream.into());
} }
Err(_) => { Err(_) => {
self.machine_st.fail = true; self.machine_st.fail = true;
@@ -4689,7 +4687,7 @@ impl Machine {
*stream.options_mut() = StreamOptions::default(); *stream.options_mut() = StreamOptions::default();
stream.options_mut().set_stream_type(StreamType::Binary); stream.options_mut().set_stream_type(StreamType::Binary);
self.indices.streams.insert(stream); self.indices.streams.insert(stream);
let stream = stream_as_cell!(stream); let stream: HeapCellValue = stream.into();
let handle: TypedArenaPtr<HttpResponse> = arena_alloc!(request.response, &mut self.machine_st.arena); let handle: TypedArenaPtr<HttpResponse> = arena_alloc!(request.response, &mut self.machine_st.arena);
@@ -4792,27 +4790,26 @@ impl Machine {
read_heap_cell!(culprit, read_heap_cell!(culprit,
(HeapCellValueTag::Cons, cons_ptr) => { (HeapCellValueTag::Cons, cons_ptr) => {
match_untyped_arena_ptr!(cons_ptr, match_untyped_arena_ptr!(cons_ptr,
(ArenaHeaderTag::HttpResponse, http_response) => { (ArenaHeaderTag::HttpResponse, http_response) => {
let mut stream = Stream::from_http_sender( let mut stream = Stream::from_http_sender(
http_response, http_response,
status_code, status_code,
headers, headers,
&mut self.machine_st.arena &mut self.machine_st.arena
);
*stream.options_mut() = StreamOptions::default();
stream.options_mut().set_stream_type(StreamType::Binary);
self.indices.streams.insert(stream);
self.machine_st.bind(stream_addr.as_var().unwrap(), stream.into());
}
_ => {
unreachable!();
}
); );
*stream.options_mut() = StreamOptions::default();
stream.options_mut().set_stream_type(StreamType::Binary);
self.indices.streams.insert(stream);
let stream = stream_as_cell!(stream);
self.machine_st.bind(stream_addr.as_var().unwrap(), stream);
}
_ => {
unreachable!();
}
);
} }
_ => { _ => {
unreachable!(); unreachable!();
} }
); );
@@ -5125,7 +5122,7 @@ impl Machine {
let stream_var = self.deref_register(3); let stream_var = self.deref_register(3);
self.machine_st self.machine_st
.bind(stream_var.as_var().unwrap(), stream_as_cell!(stream)); .bind(stream_var.as_var().unwrap(), stream.into());
} else { } else {
let err = self let err = self
.machine_st .machine_st
@@ -6559,7 +6556,7 @@ impl Machine {
self.indices.streams.insert(stream); self.indices.streams.insert(stream);
stream_as_cell!(stream) HeapCellValue::from(stream)
} }
Err(ErrorKind::PermissionDenied) => { Err(ErrorKind::PermissionDenied) => {
return Err(self.machine_st.open_permission_error( return Err(self.machine_st.open_permission_error(
@@ -6715,14 +6712,13 @@ impl Machine {
self.indices.streams.insert(tcp_stream); self.indices.streams.insert(tcp_stream);
let tcp_stream = stream_as_cell!(tcp_stream);
let client = atom_as_cell!(client); let client = atom_as_cell!(client);
let client_addr = self.deref_register(2); let client_addr = self.deref_register(2);
let stream_addr = self.deref_register(3); let stream_addr = self.deref_register(3);
self.machine_st.bind(client_addr.as_var().unwrap(), client); self.machine_st.bind(client_addr.as_var().unwrap(), client);
self.machine_st.bind(stream_addr.as_var().unwrap(), tcp_stream); self.machine_st.bind(stream_addr.as_var().unwrap(), tcp_stream.into());
} }
None => { None => {
self.machine_st.fail = true; self.machine_st.fail = true;
@@ -6770,10 +6766,11 @@ impl Machine {
let stream = Stream::from_tls_stream(addr, stream, &mut self.machine_st.arena); let stream = Stream::from_tls_stream(addr, stream, &mut self.machine_st.arena);
self.indices.streams.insert(stream); self.indices.streams.insert(stream);
self.machine_st.heap.push(stream_as_cell!(stream)); // FIXME: why are we pushing a random, unreferenced cell on the heap?
self.machine_st.heap.push(stream.into());
let stream_addr = self.deref_register(3); let stream_addr = self.deref_register(3);
self.machine_st self.machine_st
.bind(stream_addr.as_var().unwrap(), stream_as_cell!(stream)); .bind(stream_addr.as_var().unwrap(), stream.into());
Ok(()) Ok(())
} else { } else {
@@ -6826,7 +6823,7 @@ impl Machine {
let stream_addr = self.deref_register(4); let stream_addr = self.deref_register(4);
self.machine_st self.machine_st
.bind(stream_addr.as_var().unwrap(), stream_as_cell!(stream)); .bind(stream_addr.as_var().unwrap(), stream.into());
} else { } else {
unreachable!(); unreachable!();
} }
@@ -6875,7 +6872,7 @@ impl Machine {
let err = self.machine_st.permission_error( let err = self.machine_st.permission_error(
Permission::Reposition, Permission::Reposition,
atom!("stream"), atom!("stream"),
stream_as_cell!(stream), HeapCellValue::from(stream),
); );
return Err(self.machine_st.error_form(err, stub)); return Err(self.machine_st.error_form(err, stub));
@@ -8099,7 +8096,7 @@ impl Machine {
let lib_stream = Stream::from_static_string(library, &mut self.machine_st.arena); let lib_stream = Stream::from_static_string(library, &mut self.machine_st.arena);
unify!( unify!(
self.machine_st, self.machine_st,
stream_as_cell!(lib_stream), HeapCellValue::from(lib_stream),
self.machine_st.registers[2] self.machine_st.registers[2]
); );

View File

@@ -218,12 +218,6 @@ macro_rules! string_as_pstr_cell {
}}; }};
} }
macro_rules! stream_as_cell {
($ptr:expr) => {
raw_ptr_as_cell!($ptr.as_ptr())
};
}
macro_rules! cell_as_stream { macro_rules! cell_as_stream {
($cell:expr) => {{ ($cell:expr) => {{
let ptr = cell_as_untyped_arena_ptr!($cell); let ptr = cell_as_untyped_arena_ptr!($cell);