diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 4cb917157..2b4606992 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -196,7 +196,7 @@ impl SizedIPCBuffer for T { fn is_cool(addr: usize, size: usize) -> bool { size == core::mem::size_of::() && - (addr % core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 && addr != 0 } unsafe fn from_raw_parts<'a>(addr: usize, _size: usize) -> &'a Self { @@ -215,7 +215,7 @@ impl SizedIPCBuffer for [T] { fn is_cool(addr: usize, size: usize) -> bool { size % core::mem::size_of::() == 0 && size != 0 && - (addr % core::mem::align_of::()) == 0 + (addr % core::mem::align_of::()) == 0 && addr != 0 } unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { @@ -229,11 +229,11 @@ impl SizedIPCBuffer for [T] { impl<'a> IPCBuffer<'a> { /// Creates a Type-A IPCBuffer from the given reference. - fn out_buffer(data: &T, flags: u8) -> IPCBuffer { + fn out_buffer(data: Option<&T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::A { flags }, @@ -242,11 +242,11 @@ impl<'a> IPCBuffer<'a> { } /// Creates a Type-B IPCBuffer from the given reference. - fn in_buffer(data: &mut T, flags: u8) -> IPCBuffer { + fn in_buffer(mut data: Option<&mut T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::B { flags }, @@ -258,11 +258,11 @@ impl<'a> IPCBuffer<'a> { /// /// If has_u16_size is true, the size of the pointer will be written after /// the raw data. This is only used when sending client-sized arrays. - fn in_pointer(data: &mut T, has_u16_size: bool) -> IPCBuffer { + fn in_pointer(mut data: Option<&mut T>, has_u16_size: bool) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::C { has_u16_size }, @@ -273,11 +273,11 @@ impl<'a> IPCBuffer<'a> { /// Creates a Type-X IPCBuffer from the given reference. /// /// The counter defines which type-C buffer this should be copied into. - fn out_pointer(data: &T, counter: u8) -> IPCBuffer { + fn out_pointer(data: Option<&T>, counter: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::X { counter }, @@ -606,13 +606,13 @@ where /// Push an OutBuffer (type-A buffer) backed by the specified data. pub fn push_out_buffer(&mut self, data: &'a T) -> &mut Self { - self.buffers.push(IPCBuffer::out_buffer(data, 0)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); self } /// Push an InBuffer (type-B buffer) backed by the specified data. pub fn push_in_buffer(&mut self, data: &'a mut T) -> &mut Self { - self.buffers.push(IPCBuffer::in_buffer(data, 0)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); self } @@ -622,7 +622,7 @@ where /// to the Raw Data. See Buffer 0xA on the IPC Marshalling page of /// switchbrew. pub fn push_in_pointer(&mut self, data: &'a mut T, has_u16_count: bool) -> &mut Self { - self.buffers.push(IPCBuffer::in_pointer(data, has_u16_count)); + self.buffers.push(IPCBuffer::in_pointer(Some(data), has_u16_count)); self } @@ -633,10 +633,35 @@ where /// switchbrew. pub fn push_out_pointer(&mut self, data: &'a T) -> &mut Self { let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); - self.buffers.push(IPCBuffer::out_pointer(data, index as u8)); + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); self } + pub fn push_in_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a mut T) -> &mut Self { + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::in_pointer(Some(data), false)); + self.buffers.push(IPCBuffer::in_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::in_pointer::(None, false)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); + } + self + } + + pub fn push_out_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a T) -> &mut Self { + let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); + self.buffers.push(IPCBuffer::out_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::out_pointer::(None, index as u8)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); + } + self + } + + + /// Send a Pid with this IPC request. /// /// If `pid` is None, sends the current process' Pid. If it's Some, then it @@ -675,6 +700,56 @@ where } } + pub unsafe fn pop_in_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + + pub unsafe fn pop_out_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b mut T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts_mut(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + // TODO: Move pack to a non-generic function // BODY: Right now the pack and unpack functions are duplicated for every // BODY: instanciation of Message. This probably has a huge penalty on diff --git a/libuser/src/ipc/server.rs b/libuser/src/ipc/server.rs index 80dc2b597..8d080ed07 100644 --- a/libuser/src/ipc/server.rs +++ b/libuser/src/ipc/server.rs @@ -292,6 +292,31 @@ where Ok(false) }, Some((2, _)) => Ok(true), + Some((5, 0)) | Some((7, 0)) => { + // ConvertCurrentObjectToDomain, unsupported + Ok(true) + }, + Some((5, 1)) | Some((7, 1)) => { + // CopyFromCurrentDomain, unsupported + Ok(true) + }, + Some((5, 2)) | Some((7, 2)) => { + // CloneCurrentObject, unsupported + Ok(true) + }, + Some((5, 3)) | Some((7, 3)) => { + // QueryPointerBufferSize + let mut msg__ = Message::::new_response(None); + msg__.push_raw(self.pointer_buf.len() as u16); + msg__.pack(&mut self.buf[..]); + self.handle.reply(&mut self.buf[..])?; + Ok(false) + }, + Some((5, 4)) | Some((7, 4)) => { + // CloneCurrentObjectEx, unsupported + Ok(true) + }, + _ => Ok(true) } } diff --git a/libuser/src/types.rs b/libuser/src/types.rs index a69fd6cc2..717742146 100644 --- a/libuser/src/types.rs +++ b/libuser/src/types.rs @@ -107,6 +107,19 @@ impl ClientSession { .map_err(|v| v.into()) } + pub fn query_pointer_buffer(&self) -> Result { + // Use a very small buffer to avoid stackoverflows - we really don't + // need a big one here anyways. + let mut data = [0; 0x10]; + let mut msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 3); + msg.set_ty(MessageTy::Control); + msg.pack(&mut data[..]); + self.send_sync_request_with_user_buffer(&mut data[..])?; + let msg = Message::::unpack(&data[..]); + msg.error()?; + Ok(msg.raw()) + } + /// Consumes the session, returning the underlying handle. Note that closing /// a Handle without sending a close IPC message will leak the object in the /// sysmodule. You should always reconstruct the ClientSession from the diff --git a/swipc-gen/src/gen_rust_code.rs b/swipc-gen/src/gen_rust_code.rs index 0fff1f907..d2333453e 100644 --- a/swipc-gen/src/gen_rust_code.rs +++ b/swipc-gen/src/gen_rust_code.rs @@ -319,9 +319,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -336,9 +336,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -514,29 +514,35 @@ fn gen_call(cmd: &Func) -> Result { match ty { Alias::Array(..) | Alias::Buffer(..) => true, _ => false })) { match item { - Alias::Array(underlying_ty, bufty) | Alias::Buffer(underlying_ty, bufty, _) => { - let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4)) { - (0b01, 0b01) => ("", "in", "buffer"), - (0b01, 0b10) => ("", "in", "pointer"), - (0b10, 0b01) => ("mut", "out", "buffer"), - (0b10, 0b10) => ("mut", "out", "pointer"), - _ => panic!("Invalid bufty") + Alias::Array(_, bufty) | Alias::Buffer(_, bufty, _) => { + let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4), bufty.get_bit(5)) { + (0b01, _, true) => ("", "in", "smart_buffer"), + (0b10, _, true) => ("mut", "out", "smart_buffer"), + (0b01, 0b01, false) => ("", "in", "buffer"), + (0b01, 0b10, false) => ("", "in", "pointer"), + (0b10, 0b01, false) => ("mut", "out", "buffer"), + (0b10, 0b10, false) => ("mut", "out", "pointer"), + (direction, ty, smart) => panic!("Invalid bufty while handling {:?}: {:x}, {:x} {} {:x}", cmd, direction, ty, smart, bufty) }; - let realty = get_type(false, underlying_ty, false)?; - if let Alias::Array(..) = item { - // TODO: Make pop_out_buffer and co safe to call. - // BODY: Currently, pop_out_buffer (and other functions of - // BODY: that family) are unsafe to call as they basically - // BODY: allow transmuting variables. We should use a crate - // BODY: like `plain` to ensure that said functions are only - // BODY: callable when it is safe. - args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<[{}]>().unwrap() }}, ", ismut, direction, ty, realty); + let realty = get_type(false, item, false)?; + let realty = if realty.starts_with("&mut") { + realty.trim_start_matches("&mut") + } else if realty.starts_with("&") { + realty.trim_start_matches("&") } else { - args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); - } + &*realty + }; + + // TODO: Make pop_out_buffer and co safe to call. + // BODY: Currently, pop_out_buffer (and other functions of + // BODY: that family) are unsafe to call as they basically + // BODY: allow transmuting variables. We should use a crate + // BODY: like `plain` to ensure that said functions are only + // BODY: callable when it is safe. + args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); }, Alias::Object(ty) => { - args += &format!("{}Proxy(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); + args += &format!("{}Proxy::from(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); }, Alias::Handle(is_copy, ty) => { let handle = if *is_copy { @@ -693,7 +699,14 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, "/// {}", line).unwrap(); } writeln!(s, "#[derive(Debug)]").unwrap(); - writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + // Detect the presence of smart buffers on this interface. If there is one + // present, we want to cache the pointer buffer size in the structure. + let has_smart_buffer = interface.funcs.iter().any(|cmd| cmd.args.iter().chain(cmd.ret.iter()).any(|(arg, _)| if let Alias::Buffer(_, ty, _) | Alias::Array(_, ty) = arg { ty.get_bit(5) } else { false } )); + if has_smart_buffer { + writeln!(s, "pub struct {}(ClientSession, u16);", struct_name).unwrap(); + } else { + writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + } writeln!(s).unwrap(); writeln!(s, "impl From<{}> for ClientSession {{", struct_name).unwrap(); writeln!(s, " fn from(sess: {}) -> ClientSession {{", struct_name).unwrap(); @@ -703,7 +716,17 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s).unwrap(); writeln!(s, "impl From for {} {{", struct_name).unwrap(); writeln!(s, " fn from(sess: ClientSession) -> {} {{", struct_name).unwrap(); - writeln!(s, " {}(sess)", struct_name).unwrap(); + + // Cache the pointer buffer size in the structure only if we have smart + // buffers on the interface. + if has_smart_buffer { + // Assume that if there's an error, the remote doesn't support the + // pointer buffer at all. + writeln!(s, " let pointer_buffer = sess.query_pointer_buffer().unwrap_or(0);").unwrap(); + writeln!(s, " {}(sess, pointer_buffer)", struct_name).unwrap(); + } else { + writeln!(s, " {}(sess)", struct_name).unwrap(); + } writeln!(s, " }}").unwrap(); writeln!(s, "}}").unwrap(); @@ -728,7 +751,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { let mut service_name = service.to_string(); service_name += &"\\0"; writeln!(s, r#" let _ = match syscalls::connect_to_named_port("{}") {{"#, service_name).unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(KernelError::NoSuchEntry) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => Err(err)?").unwrap(); writeln!(s, " }};").unwrap(); @@ -744,7 +767,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, r#" core::mem::transmute(*b"{}")"#, service_name).unwrap(); writeln!(s, " }};").unwrap(); writeln!(s, " let _ = match sunrise_libuser::sm::IUserInterfaceProxy::raw_new()?.get_service(svcname) {{").unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(Error::Sm(SmError::ServiceNotRegistered, ..)) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => return Err(err)").unwrap(); writeln!(s, " }};").unwrap();