diff --git a/crates/Cargo.toml b/crates/Cargo.toml index c062cbd7..f2f32373 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -20,6 +20,7 @@ resolver = "3" members = [ "optee-teec", "optee-teec-macros", + "optee-teec-plugin-bindgen", "optee-teec-sys", "optee-teec-systest", "optee-utee", @@ -40,7 +41,9 @@ description = "TEE internal core API." edition = "2024" [workspace.dependencies] +optee-teec = { version = "0.8.0", path = "optee-teec" } optee-teec-macros = { version = "0.8.0", path = "optee-teec-macros" } +optee-teec-plugin-bindgen = { version = "0.8.0", path = "optee-teec-plugin-bindgen" } optee-teec-sys = { version = "0.8.0", path = "optee-teec-sys" } optee-utee = { version = "0.8.0", path = "optee-utee" } optee-utee-macros = { version = "0.8.0", path = "optee-utee-macros" } @@ -52,8 +55,10 @@ uuid = { version = "1.23", default-features = false } hex = { version = "0.4", default-features = false, features = ["alloc"] } quote = "1.0" syn = { version = "2.0", features = ["full"] } +proc-macro2 = "1.0.92" libc = "0.2" rand = "0.10" once_cell = "1.20.2" serde = { version = "1.0.228" } serde_json = { version = "1.0.149" } +log = "0.4.29" diff --git a/crates/optee-teec-macros/Cargo.toml b/crates/optee-teec-macros/Cargo.toml index 570ff1e9..f3d71e79 100644 --- a/crates/optee-teec-macros/Cargo.toml +++ b/crates/optee-teec-macros/Cargo.toml @@ -30,3 +30,7 @@ proc-macro = true [dependencies] quote.workspace = true syn.workspace = true +optee-teec-plugin-bindgen.workspace = true + +[dev-dependencies] +optee-teec.workspace = true diff --git a/crates/optee-teec-macros/src/lib.rs b/crates/optee-teec-macros/src/lib.rs index dabd46bb..b707a1d7 100644 --- a/crates/optee-teec-macros/src/lib.rs +++ b/crates/optee-teec-macros/src/lib.rs @@ -15,22 +15,25 @@ // specific language governing permissions and limitations // under the License. -#![recursion_limit = "128"] - extern crate proc_macro; +use optee_teec_plugin_bindgen::{DEFAULT_INIT_FN_NAME, DEFAULT_INVOKE_FN_NAME}; use proc_macro::TokenStream; -use quote::{ToTokens, quote}; -use syn::parse_macro_input; +use quote::quote; use syn::spanned::Spanned; +use syn::{FnArg, parse_macro_input}; -/// Attribute to declare the init function of a plugin +/// Attribute to derive the injected init function from an existing function /// ``` no_run -/// #[plugin_init] -/// fn plugin_init() -> Result<()> {} +/// use optee_teec_macros::derive_raw_plugin_init; +/// +/// #[derive_raw_plugin_init] +/// fn plugin_init() -> optee_teec::Result<()> { +/// Ok(()) +/// } /// ``` #[proc_macro_attribute] -pub fn plugin_init(_args: TokenStream, input: TokenStream) -> TokenStream { +pub fn derive_raw_plugin_init(_args: TokenStream, input: TokenStream) -> TokenStream { let f = parse_macro_input!(input as syn::ItemFn); let f_vis = &f.vis; let f_block = &f.block; @@ -55,12 +58,15 @@ pub fn plugin_init(_args: TokenStream, input: TokenStream) -> TokenStream { .into(); } + let bindgen_fn_name = quote::format_ident!("{}", DEFAULT_INIT_FN_NAME); + let origin_fn_name = &f_sig.ident; quote!( - pub fn _plugin_init() -> optee_teec::raw::TEEC_Result { - fn inner() -> optee_teec::Result<()> { - #f_block - } - match inner() { + #f_vis #f_sig { + #f_block + } + const _: fn() -> optee_teec::Result<()> = #origin_fn_name; + unsafe extern "C" fn #bindgen_fn_name() -> optee_teec::raw::TEEC_Result { + match #origin_fn_name() { Ok(()) => optee_teec::raw::TEEC_SUCCESS, Err(err) => err.raw_code(), } @@ -71,25 +77,54 @@ pub fn plugin_init(_args: TokenStream, input: TokenStream) -> TokenStream { // check if return_type of the function is `optee_teec::Result<()>` fn check_return_type(item_fn: &syn::ItemFn) -> bool { + const EXPECTED: [&str; 2] = ["optee_teec", "Result"]; if let syn::ReturnType::Type(_, return_type) = item_fn.sig.output.to_owned() && let syn::Type::Path(path) = return_type.as_ref() { - let expected_type = quote! { optee_teec::Result<()> }; - let actual_type = path.path.to_token_stream(); - if expected_type.to_string() == actual_type.to_string() { - return true; - } + return check_path_might_match(&path.path, &EXPECTED); } false } -/// Attribute to declare the invoke function of a plugin +// check path match the expected values +// it might still fail if developers re-export crate as other name. +fn check_path_might_match(path: &syn::Path, exp: &[&str]) -> bool { + path.segments.len() <= exp.len() + && path + .segments + .iter() + .zip(&exp[exp.len() - path.segments.len()..]) + .all(|(seg, exp)| seg.ident == *exp) +} + +fn check_invoke_fn_params(item_fn: &syn::ItemFn) -> bool { + if item_fn.sig.inputs.len() != 1 { + return false; + } + + let arg = item_fn.sig.inputs.first().expect("Infallible"); + if let FnArg::Typed(typ) = arg + && let syn::Type::Reference(typ_ref) = typ.ty.as_ref() + && typ_ref.mutability.is_some() + && let syn::Type::Path(inner_type) = typ_ref.elem.as_ref() + { + const EXPECTED: [&str; 2] = ["optee_teec", "PluginParameters"]; + return check_path_might_match(&inner_type.path, &EXPECTED); + } + false +} + +/// Attribute to derive the injected invoke function from an existing function /// ``` no_run -/// #[plugin_invoke] -/// fn plugin_invoke(params: &mut PluginParameters) {} +/// use optee_teec_macros::derive_raw_plugin_invoke; +/// +/// #[derive_raw_plugin_invoke] +/// fn plugin_invoke(params: &mut optee_teec::PluginParameters) -> optee_teec::Result<()> { +/// Ok(()) +/// } /// ``` #[proc_macro_attribute] -pub fn plugin_invoke(_args: TokenStream, input: TokenStream) -> TokenStream { +pub fn derive_raw_plugin_invoke(_args: TokenStream, input: TokenStream) -> TokenStream { let f = parse_macro_input!(input as syn::ItemFn); let f_vis = &f.vis; let f_block = &f.block; @@ -103,7 +138,8 @@ pub fn plugin_invoke(_args: TokenStream, input: TokenStream) -> TokenStream { && f_inputs.len() == 1 && f_sig.generics.where_clause.is_none() && f_sig.variadic.is_none() - && check_return_type(&f); + && check_return_type(&f) + && check_invoke_fn_params(&f); if !valid_signature { return syn::parse::Error::new( @@ -117,97 +153,38 @@ pub fn plugin_invoke(_args: TokenStream, input: TokenStream) -> TokenStream { .into(); } - let params = f_inputs - .first() - .expect("we have already verified its len") - .into_token_stream(); + let bindgen_fn_name = quote::format_ident!("{}", DEFAULT_INVOKE_FN_NAME); + let origin_fn_name = &f_sig.ident; quote!( - /// # Safety - /// - /// The `_plugin_invoke` function is the `extern "C"` entrypoint called by OP-TEE OS. - /// This SDK allows developers to implement the inner logic for a Normal World plugin in Rust. - /// More about plugins: - /// https://optee.readthedocs.io/en/latest/architecture/globalplatform_api.html#loadable-plugins-framework - /// - /// According to Clippy checks, any FFI function taking raw pointers as parameters - /// must be marked `unsafe`. This applies here because the function directly - /// dereferences `data` and `out_len`. - /// - /// ## Security Assumptions - /// The caller (OP-TEE OS) must ensure: - /// - `data` points to valid memory for reads and writes of at least `in_len` bytes - /// - `out_len` is a valid, writable, and properly aligned pointer to a `u32` (cannot be null) - /// - If `in_len == 0`, `data` may be null; otherwise it must be non-null - /// - The memory region pointed to by `data` must not be modified by other threads - /// or processes during plugin execution - /// - /// Additional guarantees enforced by `PluginParameters` in this SDK: - /// - If `data` is null and `in_len` is 0, it is treated as an empty input buffer; - /// the inner logic (developer code) should consider this case - /// - If the output length exceeds `in_len`, it will be rejected and a short buffer - /// error returned, with the required `out_len` set - /// - Input and output share the same buffer, so overlap is intentional and safely - /// handled by [`PluginParameters::set_buf_from_slice`] when `out_len <= in_len` - /// - If no output is set for a success call, `out_len` will be `0` - /// - /// ## Usage Scenarios - /// - **Valid empty call**: `data = null`, `in_len = 0` → allowed (empty input to inner) - /// - **Normal call**: `data` points to a buffer of size `in_len`; if `out_len <= in_len`, - /// the plugin writes up to `in_len` bytes and updates `*out_len`; if `out_len > in_len`, - /// it is rejected with a short buffer error - /// - **Buffer overflow attempt**: if inner logic (developer code) tries to return - /// more bytes than `in_len` → rejected by `set_buf_from_slice`, error returned with required `out_len` - /// - **Invalid pointers**: null pointers are checked, but other invalid cases of pointers - /// such as dangling, misaligned, or read-only pointers will cause undefined behavior - /// and must be prevented by the caller - pub unsafe fn _plugin_invoke( + #f_vis #f_sig { + #f_block + } + const _: fn(_: &mut optee_teec::PluginParameters) -> optee_teec::Result<()> = #origin_fn_name; + + unsafe extern "C" fn #bindgen_fn_name( cmd: u32, sub_cmd: u32, - data: *mut core::ffi::c_char, - in_len: u32, - out_len: *mut u32, + data: *mut core::ffi::c_void, + in_len: optee_teec::raw::size_t, + out_len: *mut optee_teec::raw::size_t, ) -> optee_teec::raw::TEEC_Result { - fn inner(#params) -> optee_teec::Result<()> { - #f_block - } - - // Check for null pointers - if data.is_null() && in_len != 0 { - return optee_teec::raw::TEEC_ERROR_BAD_PARAMETERS; - } - if out_len.is_null() { - return optee_teec::raw::TEEC_ERROR_BAD_PARAMETERS; - } - - // Prepare input buffer - // `data` is guaranteed to be non-null if `in_len > 0` (checked above) - // If `data` is null, `in_len` must be 0, so we create an empty slice - // Otherwise, we create a mutable slice from the raw pointer and length - let inbuf = if data.is_null() { - &mut [] - } else { - // SAFETY: from_raw_parts_mut() is unsafe, but avoids copying the memory - // (which is unacceptable for large io buffers). - // Note that the caller must ensure the memory is consistent during the plugin execution. - std::slice::from_raw_parts_mut(data, in_len as usize) + let mut parameter = match unsafe { + optee_teec::PluginParameters::from_raw( + cmd, + sub_cmd, + data, + in_len, + out_len, + ) + } { + Ok(v) => v, + Err(err) => return err.raw_code(), }; - let mut params = optee_teec::PluginParameters::new(cmd, sub_cmd, inbuf); - match inner(&mut params) { - Ok(()) => { - *out_len = params.get_required_out_len() as u32; - optee_teec::raw::TEEC_SUCCESS - } - Err(err) => { - if err.kind() == optee_teec::ErrorKind::ShortBuffer { - // Inform the caller about the required buffer size - *out_len = params.get_required_out_len() as u32; - optee_teec::raw::TEEC_ERROR_SHORT_BUFFER - } else { - err.raw_code() - } - } + match #origin_fn_name(&mut parameter) { + Ok(()) => optee_teec::raw::TEEC_SUCCESS, + Err(err) => err.raw_code(), } } ) diff --git a/crates/optee-teec-plugin-bindgen/Cargo.toml b/crates/optee-teec-plugin-bindgen/Cargo.toml new file mode 100644 index 00000000..f2508db2 --- /dev/null +++ b/crates/optee-teec-plugin-bindgen/Cargo.toml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "optee-teec-plugin-bindgen" +description = "generate binding for teec plugin" +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +quote.workspace = true +proc-macro2.workspace = true +uuid.workspace = true +syn.workspace = true diff --git a/crates/optee-teec-plugin-bindgen/src/lib.rs b/crates/optee-teec-plugin-bindgen/src/lib.rs new file mode 100644 index 00000000..2bd02bf1 --- /dev/null +++ b/crates/optee-teec-plugin-bindgen/src/lib.rs @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::path::PathBuf; +pub use uuid; + +pub const DEFAULT_INIT_FN_NAME: &str = "__plugin_bindgen_init"; +pub const DEFAULT_INVOKE_FN_NAME: &str = "__plugin_bindgen_invoke"; + +pub struct Config { + name: String, + uuid: uuid::Uuid, + init_fn_name: String, + invoke_fn_name: String, + dest: Option, +} + +impl Config { + pub fn new(uuid: uuid::Uuid) -> Self { + Self { + name: env!("CARGO_PKG_NAME").to_string(), + uuid, + init_fn_name: DEFAULT_INIT_FN_NAME.to_owned(), + invoke_fn_name: DEFAULT_INVOKE_FN_NAME.to_owned(), + dest: None, + } + } + pub fn with_name(mut self, name: &str) -> Self { + self.name = name.to_string(); + self + } + pub fn with_init_fn_name(mut self, fn_name: &str) -> Self { + self.init_fn_name = fn_name.to_string(); + self + } + pub fn with_invoke_fn_name(mut self, fn_name: &str) -> Self { + self.invoke_fn_name = fn_name.to_string(); + self + } + pub fn build(self) -> std::io::Result<()> { + let codes = generate_binding( + &self.name, + &self.uuid, + &self.init_fn_name, + &self.invoke_fn_name, + ) + .to_string(); + let out_path = self.get_out_path(); + if let Ok(v) = std::fs::read(&out_path) + && v.eq(codes.as_bytes()) + { + return Ok(()); + } + + if let Some(parent_dir) = out_path.parent() { + std::fs::create_dir_all(parent_dir)?; + } + std::fs::write(out_path, codes.as_bytes()) + } +} + +impl Config { + fn get_out_path(&self) -> PathBuf { + match self.dest.as_ref() { + Some(v) => v.clone(), + None => { + let out_dir = PathBuf::from( + std::env::var("OUT_DIR").expect("Infallible when using in build.rs"), + ); + out_dir.join("plugin_static.rs") + } + } + } +} + +pub fn generate_binding( + name: &str, + uuid: &uuid::Uuid, + init_fn_name: &str, + invoke_fn_name: &str, +) -> proc_macro2::TokenStream { + let (uuid_f1, uuid_f2, uuid_f3, uuid_f4) = uuid.as_fields(); + let name_bytes_with_null = format!("{}\0", name); + let init_fn_name = quote::format_ident!("{}", init_fn_name); + let invoke_fn_name = quote::format_ident!("{}", invoke_fn_name); + quote::quote! { + const _: () = { + use optee_teec::raw::{PluginMethod, TEEC_UUID}; + + static PLUGIN_NAME: &str = #name_bytes_with_null; + + #[unsafe(no_mangle)] + pub static mut plugin_method: PluginMethod = PluginMethod { + name: PLUGIN_NAME.as_ptr() as *const _, + uuid: TEEC_UUID { + timeLow: #uuid_f1, + timeMid: #uuid_f2, + timeHiAndVersion: #uuid_f3, + clockSeqAndNode: [#(#uuid_f4),*], + }, + init: #init_fn_name, + invoke: #invoke_fn_name, + }; + }; + } +} diff --git a/crates/optee-teec-sys/src/lib.rs b/crates/optee-teec-sys/src/lib.rs index a4e374ed..f667972d 100644 --- a/crates/optee-teec-sys/src/lib.rs +++ b/crates/optee-teec-sys/src/lib.rs @@ -17,7 +17,9 @@ #![allow(non_camel_case_types, non_snake_case)] +pub use plugin_method::*; pub use tee_client_api::*; pub type size_t = usize; +mod plugin_method; mod tee_client_api; diff --git a/examples/supp_plugin-rs/plugin/plugin_static.rs b/crates/optee-teec-sys/src/plugin_method.rs similarity index 66% rename from examples/supp_plugin-rs/plugin/plugin_static.rs rename to crates/optee-teec-sys/src/plugin_method.rs index 2378e4f1..dfa61209 100644 --- a/examples/supp_plugin-rs/plugin/plugin_static.rs +++ b/crates/optee-teec-sys/src/plugin_method.rs @@ -15,13 +15,19 @@ // specific language governing permissions and limitations // under the License. -#[no_mangle] -pub static mut plugin_method: optee_teec::PluginMethod = optee_teec::PluginMethod { - name: plugin_name.as_ptr(), - uuid: PLUGIN_UUID_STRUCT, - init: _plugin_init, - invoke: _plugin_invoke, -}; +use crate::{TEEC_Result, TEEC_UUID, size_t}; +use core::ffi::{c_char, c_void}; -#[no_mangle] -pub static plugin_name: &[u8] = b"syslog\0"; +#[repr(C)] +pub struct PluginMethod { + pub name: *const c_char, + pub uuid: TEEC_UUID, + pub init: unsafe extern "C" fn() -> TEEC_Result, + pub invoke: unsafe extern "C" fn( + cmd: u32, + sub_cmd: u32, + data: *mut c_void, + in_len: size_t, + out_len: *mut size_t, + ) -> TEEC_Result, +} diff --git a/crates/optee-teec-systest/build.rs b/crates/optee-teec-systest/build.rs index 7e7b130f..367014a5 100644 --- a/crates/optee-teec-systest/build.rs +++ b/crates/optee-teec-systest/build.rs @@ -28,11 +28,15 @@ fn main() { .edition(2024) .target("aarch64-unknown-linux-gnu") .header("tee_client_api.h") + .header("tee_plugin_method.h") .include(path.display().to_string()) .rename_struct_ty(|ty| { if ty.starts_with("TEEC") { return Some(ty.to_string()); } + if ty.eq("PluginMethod") { + return Some("struct plugin_method".to_string()); + } None }) .rename_union_ty(|ty| { diff --git a/crates/optee-teec/Cargo.toml b/crates/optee-teec/Cargo.toml index 5035ebe0..5814b7a4 100644 --- a/crates/optee-teec/Cargo.toml +++ b/crates/optee-teec/Cargo.toml @@ -26,10 +26,10 @@ edition.workspace = true [dependencies] optee-teec-sys.workspace = true -optee-teec-macros.workspace = true uuid.workspace = true hex.workspace = true num_enum.workspace = true +log.workspace = true [dev-dependencies] optee-teec-sys = { workspace = true, features = ["no_link"] } diff --git a/crates/optee-teec/src/extension.rs b/crates/optee-teec/src/extension.rs index 8f50b49f..2da88685 100644 --- a/crates/optee-teec/src/extension.rs +++ b/crates/optee-teec/src/extension.rs @@ -15,65 +15,155 @@ // specific language governing permissions and limitations // under the License. -use crate::raw; -use crate::{Error, ErrorKind, Result}; -use core::ffi::c_char; +use crate::{ + raw::size_t, + {ErrorKind, Result}, +}; +use core::ffi::c_void; -#[repr(C)] -pub struct PluginMethod { - pub name: *const c_char, - pub uuid: raw::TEEC_UUID, - pub init: fn() -> raw::TEEC_Result, - pub invoke: unsafe fn( - cmd: u32, - sub_cmd: u32, - data: *mut c_char, - in_len: u32, - out_len: *mut u32, - ) -> raw::TEEC_Result, -} - -/// struct PluginParameters { -/// @cmd: u32, plugin cmd, defined in proto/ -/// @sub_cmd: u32, plugin subcmd, defined in proto/ -/// @inout: &'a mut [u8], input/output buffer shared with TA and plugin -/// @required_outlen, length of output sent to TA -/// } -pub struct PluginParameters<'a> { +/// Parameters for a plugin invocation, carrying the command, sub-command, +/// and the inout buffer. +/// +/// The core design goal of this struct is to prevent developers from forgetting +/// to set `out_len`. In the C ABI, `out_len` is a raw pointer that the plugin +/// must write to in order to report how many bytes it actually +/// produced. Forgetting to set it is a silent bug — the TA caller receives +/// garbage (uninitialized or stale) length, leading to buffer over-reads or +/// truncated output that is extremely hard to diagnose. +/// +/// To eliminate this class of bugs, `PluginParameters` ties `out_len` to +/// every output-writing operation: `write_output_at` and `set_buf_from_slice`, +/// both automatically update `out_len` on success, so the plugin developers +/// never has to do it manually. If plugin developers need full control, they +/// can use `get_buffer_mut` and `set_out_len` explicitly. +pub struct PluginParameters<'a, 'b> { + /// Command identifier for the plugin invocation. pub cmd: u32, + /// Sub-command identifier for the plugin invocation. pub sub_cmd: u32, - pub inout: &'a mut [u8], - required_outlen: usize, + /// Inout buffer that carries input data into the plugin and receives + /// output data from it. + buf: &'a mut [u8], + /// Pointer to the output length that the plugin must set. + /// Wrapped in `Option` because the C API allows it to be NULL + /// (meaning the caller does not expect output). When present, + /// every output-writing method automatically updates this value, + /// ensuring `out_len` is never left unset. + out_len: Option<&'b mut size_t>, } -impl<'a> PluginParameters<'a> { - pub fn new(cmd: u32, sub_cmd: u32, inout: &'a mut [u8]) -> Self { - Self { + +impl<'a, 'b> PluginParameters<'a, 'b> { + /// Constructs a `PluginParameters` from raw C pointers. + /// + /// # Safety + /// - `buf` must be valid for reads/writes of `in_len` bytes if not null + /// - `out_len` must be valid for writes if not null + /// - both pointers must remain alive for the lifetime of the returned + /// `PluginParameters` + /// + /// When `out_len` is non-null, it will be tracked by the returned struct + /// so that output-writing methods can update it automatically — this is + /// the key mechanism that prevents the "forgot to set out_len" bug. + pub unsafe fn from_raw( + cmd: u32, + sub_cmd: u32, + buf: *mut c_void, + in_len: size_t, + out_len: *mut size_t, + ) -> Result { + // Reject obviously invalid parameter combinations: + // a non-zero in_len or a present out_len implies the caller expects + // to use the buffer, so a null buf pointer is illegal. + if (in_len != 0 || !out_len.is_null()) && buf.is_null() { + return Err(ErrorKind::BadParameters.into()); + } + // Wrap the raw buffer pointer into a safe slice. + // For current OP-TEE, buf should always be non-null. + let buf = match buf.is_null() { + true => &mut [], + false => unsafe { core::slice::from_raw_parts_mut(buf as *mut _, in_len) }, + }; + // Track the out_len pointer so output-writing methods can update it + // automatically — this is what prevents "forgot to set out_len" bugs. + let out_len = unsafe { out_len.as_mut() }; + + Ok(Self { cmd, sub_cmd, - inout, - required_outlen: 0_usize, - } + buf, + out_len, + }) } - // This function copies data from the provided slice to the inout buffer - // Please note that the Result should be properly handled by the caller - // If the inout buffer is smaller than the provided slice, an error will be returned - // Ignoring this error will lead to undefined behavior + /// Copies the entire `sendslice` into the inout buffer starting at offset + /// 0, and automatically sets `out_len` to `sendslice.len()`. + /// + /// This is the primary safe way to write output — callers do not need to + /// update `out_len` separately. + /// + /// Returns `ShortBuffer` if the buffer is too small, or `BadState` if + /// the output length pointer is not available. pub fn set_buf_from_slice(&mut self, sendslice: &[u8]) -> Result<()> { - if self.inout.len() < sendslice.len() { - println!("Overflow: Input length is less than output length"); - self.required_outlen = sendslice.len(); - return Err(Error::new(ErrorKind::ShortBuffer)); + self.write_output_at(0, sendslice) + } + + /// Writes `data` into the inout buffer at the given `pos`, and + /// automatically updates `out_len` to `pos + data.len()`. + /// + /// By always updating `out_len` on a successful write, this method + /// eliminates the risk of the developer forgetting to set it. + /// + /// Returns `ShortBuffer` if the buffer is too small, or `BadState` if + /// the output length pointer is not available. + pub fn write_output_at(&mut self, pos: usize, data: &[u8]) -> Result<()> { + if let Some(out_len) = self.out_len.as_mut() { + let dest_len = pos + data.len(); + if self.buf.len() < dest_len { + // Buffer overflow: not enough space for the write + log::debug!("Overflow: Input length is less than output length"); + return Err(ErrorKind::ShortBuffer.into()); + } + self.buf[pos..dest_len].copy_from_slice(data); + (**out_len) = dest_len; + return Ok(()); } - self.required_outlen = sendslice.len(); - self.inout[..self.required_outlen].copy_from_slice(sendslice); - Ok(()) + log::debug!("output is not allowed"); + Err(ErrorKind::BadState.into()) + } + + /// Returns a shared reference to the inout buffer. + pub fn get_buffer(&self) -> &[u8] { + self.buf } - /// This function returns the required output length - /// If the inout buffer is too small, this indicates the size needed - /// If the inout buffer is large enough, this is the actual output length - pub fn get_required_out_len(&self) -> usize { - self.required_outlen + /// Returns a mutable reference to the inout buffer. + /// + /// # Safety + /// The caller is responsible for updating `out_len` (via [`set_out_len`]) + /// after writing to the buffer. + pub unsafe fn get_buffer_mut(&mut self) -> &mut [u8] { + self.buf + } + + /// Explicitly sets `out_len` to the given value. + /// + /// This is an escape hatch for cases where the caller needs full control + /// over the output length (e.g. after using `get_buffer_mut`). In most + /// cases should prefer `write_output_at` or `set_buf_from_slice`, which set + /// `out_len` automatically and avoid the "forgot to set out_len" bug. + /// + /// Returns `BadParameters` if `out_len` exceeds the buffer size, or + /// `BadState` if the output length pointer is not available. + pub fn set_out_len(&mut self, out_len: usize) -> Result<()> { + if out_len > self.buf.len() { + return Err(ErrorKind::BadParameters.into()); + } + match self.out_len.as_mut() { + None => Err(ErrorKind::BadState.into()), + Some(v) => { + **v = out_len; + Ok(()) + } + } } } diff --git a/crates/optee-teec/src/lib.rs b/crates/optee-teec/src/lib.rs index 5220ce15..501b1578 100644 --- a/crates/optee-teec/src/lib.rs +++ b/crates/optee-teec/src/lib.rs @@ -22,7 +22,6 @@ pub use self::operation::Operation; pub use self::parameter::{Param, ParamNone, ParamTmpRef, ParamType, ParamTypes, ParamValue}; pub use self::session::{ConnectionMethods, Session}; pub use self::uuid::Uuid; -pub use optee_teec_macros::{plugin_init, plugin_invoke}; // Re-export optee_teec_sys so developers don't have to add it to their cargo // dependencies. pub use optee_teec_sys as raw; diff --git a/crates/optee-utee-build/Cargo.toml b/crates/optee-utee-build/Cargo.toml index 8f0050cb..2bb2ec39 100644 --- a/crates/optee-utee-build/Cargo.toml +++ b/crates/optee-utee-build/Cargo.toml @@ -26,7 +26,7 @@ edition.workspace = true [dependencies] quote.workspace = true -proc-macro2 = "1.0.92" +proc-macro2.workspace = true syn.workspace = true prettyplease = "0.2.25" uuid.workspace = true diff --git a/examples/supp_plugin-rs/plugin/Cargo.toml b/examples/supp_plugin-rs/plugin/Cargo.toml index 1d0410f3..01b9cabb 100644 --- a/examples/supp_plugin-rs/plugin/Cargo.toml +++ b/examples/supp_plugin-rs/plugin/Cargo.toml @@ -28,9 +28,11 @@ edition = "2018" libc = "0.2.48" proto = { path = "../proto" } optee-teec = { path = "../../../crates/optee-teec" } +optee-teec-macros = { path = "../../../crates/optee-teec-macros" } [build-dependencies] -uuid = { version = "0.8" } +optee-teec-plugin-bindgen = { path = "../../../crates/optee-teec-plugin-bindgen" } +uuid = { version = "1.23" } proto = { path = "../proto" } anyhow = "1.0" @@ -42,4 +44,4 @@ crate-type = ["cdylib"] name = "syslog_plugin" [package.metadata.optee.plugin] -uuid-path = "../plugin_uuid.txt" \ No newline at end of file +uuid-path = "../plugin_uuid.txt" diff --git a/examples/supp_plugin-rs/plugin/build.rs b/examples/supp_plugin-rs/plugin/build.rs index fc924fcc..1bb065f7 100644 --- a/examples/supp_plugin-rs/plugin/build.rs +++ b/examples/supp_plugin-rs/plugin/build.rs @@ -14,33 +14,12 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. +use optee_teec_plugin_bindgen::{uuid::Uuid, Config}; -use anyhow::{anyhow, Result}; -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; -use uuid::Uuid; - -fn main() -> Result<()> { - let out = &PathBuf::from(env::var_os("OUT_DIR").ok_or_else(|| anyhow!("OUT_DIR not set"))?); - let mut buffer = File::create(out.join("plugin_static.rs"))?; - buffer.write_all(include_bytes!("plugin_static.rs"))?; - - let plugin_uuid = Uuid::parse_str(proto::PLUGIN_UUID)?; - let (time_low, time_mid, time_hi_and_version, clock_seq_and_node) = plugin_uuid.as_fields(); - - writeln!(buffer)?; - write!( - buffer, - "const PLUGIN_UUID_STRUCT: optee_teec::raw::TEEC_UUID = optee_teec::raw::TEEC_UUID {{ - timeLow: {:#x}, - timeMid: {:#x}, - timeHiAndVersion: {:#x}, - clockSeqAndNode: {:#x?}, -}};", - time_low, time_mid, time_hi_and_version, clock_seq_and_node - )?; +fn main() -> anyhow::Result<()> { + Config::new(Uuid::parse_str(proto::PLUGIN_UUID)?) + .with_name("syslog") + .build()?; Ok(()) } diff --git a/examples/supp_plugin-rs/plugin/src/lib.rs b/examples/supp_plugin-rs/plugin/src/lib.rs index 65ffe5dc..acac6c0c 100644 --- a/examples/supp_plugin-rs/plugin/src/lib.rs +++ b/examples/supp_plugin-rs/plugin/src/lib.rs @@ -15,24 +15,26 @@ // specific language governing permissions and limitations // under the License. -use optee_teec::{plugin_init, plugin_invoke, ErrorKind, PluginParameters}; +use optee_teec::{ErrorKind, Result}; +use optee_teec_macros::{derive_raw_plugin_init, derive_raw_plugin_invoke}; use proto::PluginCommand; -#[plugin_init] -fn init() -> optee_teec::Result<()> { +#[derive_raw_plugin_init] +fn init() -> Result<()> { println!("*plugin*: init, version: {}", env!("CARGO_PKG_VERSION")); Ok(()) } -#[plugin_invoke] -fn invoke(params: &mut PluginParameters) -> optee_teec::Result<()> { +#[derive_raw_plugin_invoke] +fn invoke(params: &mut optee_teec::PluginParameters) -> optee_teec::Result<()> { println!("*plugin*: invoke"); match PluginCommand::from(params.cmd) { PluginCommand::Print => { + let input = params.get_buffer(); println!( "*plugin*: receive value: {:?} length {:?}", - params.inout, - params.inout.len() + input, + input.len() ); let send_slice: [u8; 9] = [0x40; 9];