1pub mod attachments;
13pub mod context;
14pub mod fs;
15pub mod map;
16pub mod program;
17pub mod syscalls;
18
19use crate::bpf::attachments::EbpfAttachments;
20use crate::bpf::map::{BpfMapHandle, BpfMapId, WeakBpfMapHandle};
21use crate::bpf::program::{ProgramHandle, ProgramId, WeakProgramHandle};
22use crate::security;
23use crate::task::CurrentTask;
24use ebpf::MapFlags;
25use fuchsia_rcu::RcuReadScope;
26use starnix_rcu::RcuHashMap;
27use starnix_uapi::auth::{CAP_SYS_ADMIN, Capabilities};
28use starnix_uapi::bpf_map_type_BPF_MAP_TYPE_SK_STORAGE;
29use starnix_uapi::errors::Errno;
30use std::sync::Arc;
31use zerocopy::IntoBytes as _;
32
33pub(crate) fn check_cap_or_sys_admin(
40 current_task: &CurrentTask,
41 capability: Capabilities,
42) -> Result<(), Errno> {
43 if security::check_task_capable(current_task, capability).is_ok() {
44 return Ok(());
45 }
46 security::check_task_capable(current_task, CAP_SYS_ADMIN)
47}
48
49#[derive(Default)]
51pub struct EbpfState {
52 pub attachments: EbpfAttachments,
53
54 programs: RcuHashMap<ProgramId, WeakProgramHandle>,
55 maps: RcuHashMap<BpfMapId, WeakBpfMapHandle>,
56}
57
58impl EbpfState {
59 fn register_program(&self, program: &ProgramHandle) {
60 self.programs.insert(program.id(), Arc::downgrade(program));
61 }
62
63 fn unregister_program(&self, id: ProgramId) {
64 self.programs.remove(&id).expect("Missing eBPF program");
65 }
66
67 fn get_next_program_id(&self, start_id: ProgramId) -> Option<ProgramId> {
68 let scope = RcuReadScope::new();
69 self.programs.keys(&scope).copied().filter(|&id| id > start_id).min()
70 }
71
72 fn get_program_by_id(&self, id: ProgramId) -> Option<ProgramHandle> {
73 let scope = RcuReadScope::new();
74 self.programs.get(&scope, &id).and_then(|p| p.upgrade())
75 }
76
77 fn register_map(&self, map: &BpfMapHandle) {
78 self.maps.insert(map.id(), Arc::downgrade(map));
79 }
80
81 fn unregister_map(&self, id: BpfMapId) {
82 self.maps.remove(&id).expect("Missing eBPF map");
83 }
84
85 fn get_next_map_id(&self, start_id: BpfMapId) -> Option<BpfMapId> {
86 let scope = RcuReadScope::new();
87 self.maps.keys(&scope).copied().filter(|&id| id > start_id).min()
88 }
89
90 fn get_map_by_id(&self, id: BpfMapId) -> Option<BpfMapHandle> {
91 let scope = RcuReadScope::new();
92 self.maps.get(&scope, &id).and_then(|m| m.upgrade())
93 }
94
95 pub fn remove_sk_storage_entries(&self, cookie: u64) {
98 let scope = RcuReadScope::new();
99 for weak_map in self.maps.values(&scope) {
100 if let Some(map) = weak_map.upgrade()
101 && map.schema.map_type == bpf_map_type_BPF_MAP_TYPE_SK_STORAGE
102 {
103 let _ = map.delete(cookie.as_bytes());
104 }
105 }
106 }
107
108 pub fn clone_sk_storage_entries(&self, parent_cookie: u64, child_cookie: u64) {
111 let scope = RcuReadScope::new();
112 for weak_map in self.maps.values(&scope) {
113 if let Some(map) = weak_map.upgrade()
114 && map.schema.map_type == bpf_map_type_BPF_MAP_TYPE_SK_STORAGE
115 && map.schema.flags.contains(MapFlags::Clone)
116 {
117 if let Some(mut val) = map.load(parent_cookie.as_bytes()) {
118 let _ = map.update(child_cookie.as_bytes(), (&mut val[..]).into(), 0);
119 }
120 }
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::bpf::map::BpfMap;
129 use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
130 use crate::testing::spawn_kernel_and_run;
131 use ebpf::MapSchema;
132 use starnix_uapi::bpf_map_type_BPF_MAP_TYPE_ARRAY;
133 use std::sync::atomic::{AtomicBool, Ordering};
134
135 const PARENT_COOKIE_BASE: u64 = 10_000;
136 const CHILD_COOKIE_BASE: u64 = 20_000;
137 const STRESS_WRITER_ITERATIONS: usize = 50;
138 const STRESS_READER_THREADS: u64 = 2;
139 const RELEASER_DRAIN_INTERVAL: u32 = 16;
140
141 fn create_sk_storage_map(current_task: &CurrentTask, clone_flag: bool) -> BpfMapHandle {
142 let mut flags = MapFlags::NoPrealloc;
143 if clone_flag {
144 flags |= MapFlags::Clone;
145 }
146 let schema = MapSchema {
147 map_type: bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
148 key_size: 4,
149 value_size: 8,
150 max_entries: 0,
151 flags,
152 };
153 BpfMap::new(current_task, schema, "sk_map", security::bpf_map_alloc(current_task))
154 .expect("create sk_storage map")
155 }
156
157 #[::fuchsia::test]
158 async fn test_sk_storage_clone_and_remove() {
159 spawn_kernel_and_run(async |current_task| {
160 let ebpf_state = ¤t_task.kernel().ebpf_state;
161 let cloneable_map = create_sk_storage_map(current_task, true);
162 let non_cloneable_map = create_sk_storage_map(current_task, false);
163 let array_map = BpfMap::new(
164 current_task,
165 MapSchema {
166 map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
167 key_size: 4,
168 value_size: 8,
169 max_entries: 1,
170 flags: MapFlags::empty(),
171 },
172 "arr_map",
173 security::bpf_map_alloc(current_task),
174 )
175 .expect("create array map");
176
177 assert_eq!(ebpf_state.maps.len(), 3);
178 assert!(ebpf_state.get_map_by_id(cloneable_map.id()).is_some());
179 assert_eq!(
180 ebpf_state.get_next_map_id(cloneable_map.id()),
181 Some(non_cloneable_map.id())
182 );
183 assert_eq!(ebpf_state.get_next_map_id(non_cloneable_map.id()), Some(array_map.id()));
184 assert_eq!(ebpf_state.get_next_map_id(array_map.id()), None);
185
186 let parent_cookie: u64 = 1001;
187 let child_cookie: u64 = 1002;
188 let mut val1 = 0xdeadbeef_u64.to_ne_bytes();
189 let mut val2 = 0xcafebabe_u64.to_ne_bytes();
190
191 cloneable_map
192 .update(parent_cookie.as_bytes(), (&mut val1[..]).into(), 0)
193 .expect("insert parent in cloneable_map");
194 non_cloneable_map
195 .update(parent_cookie.as_bytes(), (&mut val2[..]).into(), 0)
196 .expect("insert parent in non_cloneable_map");
197
198 {
202 let _maps_guard = ebpf_state.maps.lock();
203 ebpf_state.clone_sk_storage_entries(parent_cookie, child_cookie);
204 }
205
206 assert_eq!(cloneable_map.load(child_cookie.as_bytes()).as_deref(), Some(&val1[..]));
207 assert!(non_cloneable_map.load(child_cookie.as_bytes()).is_none());
208
209 {
210 let _maps_guard = ebpf_state.maps.lock();
211 ebpf_state.remove_sk_storage_entries(parent_cookie);
212 }
213 assert!(cloneable_map.load(parent_cookie.as_bytes()).is_none());
214 assert!(non_cloneable_map.load(parent_cookie.as_bytes()).is_none());
215 assert_eq!(cloneable_map.load(child_cookie.as_bytes()).as_deref(), Some(&val1[..]));
216
217 drop(array_map);
218 drop(cloneable_map);
219 drop(non_cloneable_map);
220 current_task.trigger_delayed_releaser();
221 fuchsia_rcu::rcu_run_callbacks();
222
223 assert!(ebpf_state.maps.is_empty());
224 })
225 .await;
226 }
227
228 #[::fuchsia::test]
229 async fn test_concurrent_sk_storage_register_unregister_and_lookup() {
230 spawn_kernel_and_run(async |current_task| {
231 let kernel = current_task.kernel();
232 let done = Arc::new(AtomicBool::new(false));
233
234 let mut reader_results = Vec::new();
235 for reader_idx in 0..STRESS_READER_THREADS {
236 let done_clone = done.clone();
237 let (res_fn, req) = SpawnRequestBuilder::new()
238 .with_sync_closure(move |task: &CurrentTask| {
239 let ebpf_state = &task.kernel().ebpf_state;
240 let parent_cookie = PARENT_COOKIE_BASE + reader_idx;
241 let child_cookie = CHILD_COOKIE_BASE + reader_idx;
242 let mut iteration = 0u32;
243 while !done_clone.load(Ordering::Relaxed) {
244 ebpf_state.clone_sk_storage_entries(parent_cookie, child_cookie);
245 ebpf_state.remove_sk_storage_entries(child_cookie);
246 iteration = iteration.wrapping_add(1);
247 if iteration % RELEASER_DRAIN_INTERVAL == 0 {
248 task.trigger_delayed_releaser();
249 }
250 }
251 task.trigger_delayed_releaser();
252 })
253 .build_with_sync_result();
254 kernel.kthreads.spawner().spawn_from_request(req);
255 reader_results.push(res_fn);
256 }
257
258 for _ in 0..STRESS_WRITER_ITERATIONS {
259 let sk_map = create_sk_storage_map(current_task, true);
260 let array_map = BpfMap::new(
261 current_task,
262 MapSchema {
263 map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
264 key_size: 4,
265 value_size: 8,
266 max_entries: 1,
267 flags: MapFlags::empty(),
268 },
269 "arr_map",
270 security::bpf_map_alloc(current_task),
271 )
272 .expect("create array map");
273
274 let mut value = 1234u64.to_ne_bytes();
275 for reader_idx in 0..STRESS_READER_THREADS {
276 let parent_cookie = PARENT_COOKIE_BASE + reader_idx;
277 let _ = sk_map.update(parent_cookie.as_bytes(), (&mut value[..]).into(), 0);
278 }
279
280 drop(array_map);
281 drop(sk_map);
282 current_task.trigger_delayed_releaser();
283 fuchsia_rcu::rcu_run_callbacks();
284 }
285
286 done.store(true, Ordering::Relaxed);
287 for res_fn in reader_results {
288 res_fn().expect("reader thread completed");
289 }
290 current_task.trigger_delayed_releaser();
291 fuchsia_rcu::rcu_run_callbacks();
292 })
293 .await;
294 }
295}