Skip to main content

starnix_core/bpf/
mod.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implementation of (e)BPF.
6//!
7//! BPF stands for Berkeley Packet Filter and is an API introduced in BSD that allows filtering
8//! network packets by running little programs in the kernel. eBPF stands for extended BFP and
9//! is a Linux extension of BPF that allows hooking BPF programs into many different
10//! non-networking-related contexts.
11
12pub 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
33/// Checks if `current_task` has either `capability` or [`CAP_SYS_ADMIN`], matching Linux
34/// capability auditing behavior.
35///
36/// In Linux, `capability` is checked first with auditing. If `capability` is absent, an audit
37/// denial is generated for `capability`, and [`CAP_SYS_ADMIN`] is checked as a fallback with auditing.
38/// If [`CAP_SYS_ADMIN`] is also absent, an audit denial is generated for [`CAP_SYS_ADMIN`].
39pub(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/// Stores global eBPF state.
50#[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    /// Removed socket with the specified `cookie` from all `sk_storage` maps.
96    // TODO(https://fxbug.dev/496639039): Move sk_storage cleanup to Netstack.
97    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    /// Clones `sk_storage` map entries with `BPF_F_CLONE` from `parent_cookie` socket to
109    /// `child_cookie` socket.
110    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 = &current_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            // Hold `ebpf_state.maps.lock()` while invoking `clone_sk_storage_entries` and
199            // `remove_sk_storage_entries` to verify that neither method acquires
200            // `self.maps.lock()`.
201            {
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}