Skip to main content

starnix_core/bpf/
attachments.rs

1// Copyright 2025 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// TODO(https://github.com/rust-lang/rust/issues/39371): remove
6#![allow(non_upper_case_globals)]
7
8use crate::bpf::check_cap_or_sys_admin;
9use crate::bpf::context::EbpfRunContextImpl;
10use crate::bpf::fs::{BpfHandle, get_bpf_object};
11use crate::bpf::program::ProgramHandle;
12use crate::mm::PAGE_SIZE;
13use crate::task::CurrentTask;
14use crate::vfs::FdNumber;
15use crate::vfs::socket::{
16    SockOptValue, Socket, SocketBpfState, SocketDomain, SocketProtocol, SocketType,
17    ZxioBackedSocket,
18};
19use ebpf::{BpfValue, EbpfProgram, EbpfProgramContext, EbpfPtr, ProgramArgument, Type};
20use ebpf_api::{
21    AttachType, BPF_SOCK_ADDR_TYPE, BPF_SOCK_TYPE, BpfSockContext, CgroupSockAddrProgramContext,
22    CgroupSockOptProgramContext, CgroupSockProgramContext, CurrentTaskContext, Map, MapValueRef,
23    MapsContext, PinnedMap, ProgramType, ReturnValueContext, SocketRef,
24};
25use fidl_fuchsia_net_filter as fnet_filter;
26use fuchsia_component::client::connect_to_protocol_sync;
27use fuchsia_rcu::RcuArc;
28use linux_uapi::{bpf_sockopt, uaddr};
29use starnix_logging::{log_error, log_warn, track_stub};
30use starnix_sync::{EbpfStateLock, RcuLock};
31use starnix_syscalls::{SUCCESS, SyscallResult};
32use starnix_uapi::auth::{CAP_NET_ADMIN, Capabilities};
33use starnix_uapi::errors::{Errno, ErrnoCode, is_error_return_value};
34use starnix_uapi::{
35    CGROUP2_SUPER_MAGIC, bpf_attr__bindgen_ty_6, bpf_sock, bpf_sock_addr, errno, error, gid_t,
36    pid_t, uid_t,
37};
38use std::ops::{Deref, DerefMut};
39use std::sync::{Arc, OnceLock};
40use zerocopy::FromBytes;
41
42pub type BpfAttachAttr = bpf_attr__bindgen_ty_6;
43
44fn check_root_cgroup_fd(current_task: &CurrentTask, cgroup_fd: FdNumber) -> Result<(), Errno> {
45    let file = current_task.files().get(cgroup_fd)?;
46
47    // Check that `cgroup_fd` is from the CGROUP2 file system.
48    let is_cgroup = file.node().fs().statfs(current_task)?.f_type == CGROUP2_SUPER_MAGIC as i64;
49    if !is_cgroup {
50        log_warn!("bpf_prog_attach(BPF_PROG_ATTACH) is called with an invalid cgroup2 FD.");
51        return error!(EINVAL);
52    }
53
54    // Currently cgroup attachments are supported only for the root cgroup.
55    // TODO(https://fxbug.dev//388077431) Allow attachments to any cgroup once cgroup
56    // hierarchy is moved to starnix_core.
57    let is_root = file
58        .node()
59        .fs()
60        .maybe_root()
61        .map(|root| Arc::ptr_eq(&root.node, file.node()))
62        .unwrap_or(false);
63    if !is_root {
64        log_warn!("bpf_prog_attach(BPF_PROG_ATTACH) is supported only for root cgroup.");
65        return error!(EINVAL);
66    }
67
68    Ok(())
69}
70
71pub fn bpf_prog_attach(
72    current_task: &CurrentTask,
73    attr: BpfAttachAttr,
74) -> Result<SyscallResult, Errno> {
75    // SAFETY: reading i32 field from a union is always safe.
76    let bpf_fd = FdNumber::from_raw(attr.attach_bpf_fd as i32);
77    let object = get_bpf_object(current_task, bpf_fd)?;
78    if matches!(object, BpfHandle::ProgramStub(_)) {
79        log_warn!("Stub program. Faking successful attach");
80        return Ok(SUCCESS);
81    }
82    let program = object.as_program()?.clone();
83
84    let required_caps = get_capability_for_program(program.info.program_type)?;
85    check_cap_or_sys_admin(current_task, required_caps)?;
86
87    let attach_type = AttachType::from(attr.attach_type);
88    let program_type = program.info.program_type;
89    if attach_type.get_program_type() != program_type {
90        log_warn!(
91            "bpf_prog_attach(BPF_PROG_ATTACH): program not compatible with attach_type \
92                   attach_type: {attach_type:?}, program_type: {program_type:?}"
93        );
94        return error!(EINVAL);
95    }
96
97    if !attach_type.is_compatible_with_expected_attach_type(program.info.expected_attach_type) {
98        log_warn!(
99            "bpf_prog_attach(BPF_PROG_ATTACH): expected_attach_type didn't match attach_type \
100                   expected_attach_type: {:?}, attach_type: {:?}",
101            program.info.expected_attach_type,
102            attach_type
103        );
104        return error!(EINVAL);
105    }
106
107    // SAFETY: reading i32 field from a union is always safe.
108    let target_fd = unsafe { attr.__bindgen_anon_1.target_fd };
109    let target_fd = FdNumber::from_raw(target_fd as i32);
110
111    current_task.kernel().ebpf_state.attachments.attach_prog(
112        current_task,
113        attach_type,
114        target_fd,
115        program,
116    )
117}
118
119pub fn bpf_prog_detach(
120    current_task: &CurrentTask,
121    attr: BpfAttachAttr,
122) -> Result<SyscallResult, Errno> {
123    let attach_type = AttachType::from(attr.attach_type);
124
125    // SAFETY: reading i32 field from a union is always safe.
126    let target_fd = unsafe { attr.__bindgen_anon_1.target_fd };
127    let target_fd = FdNumber::from_raw(target_fd as i32);
128
129    current_task.kernel().ebpf_state.attachments.detach_prog(current_task, attach_type, target_fd)
130}
131
132// Wrapper for `bpf_sock_addr` used to implement `ProgramArgument` trait.
133#[repr(C)]
134pub struct BpfSockAddr<'a> {
135    sock_addr: bpf_sock_addr,
136
137    bpf_sock: &'a BpfSock<'a>,
138}
139
140impl<'a> Deref for BpfSockAddr<'a> {
141    type Target = bpf_sock_addr;
142    fn deref(&self) -> &Self::Target {
143        &self.sock_addr
144    }
145}
146
147impl<'a> DerefMut for BpfSockAddr<'a> {
148    fn deref_mut(&mut self) -> &mut Self::Target {
149        &mut self.sock_addr
150    }
151}
152
153impl<'a> ProgramArgument for &'_ mut BpfSockAddr<'a> {
154    fn get_type() -> &'static Type {
155        &*BPF_SOCK_ADDR_TYPE
156    }
157}
158
159impl<'a, 'b> SocketRef for &'a mut BpfSockAddr<'a> {
160    fn get_socket_cookie(&self) -> Option<u64> {
161        self.bpf_sock.get_socket_cookie()
162    }
163
164    fn get_socket_uid(&self) -> Option<uid_t> {
165        self.bpf_sock.get_socket_uid()
166    }
167}
168
169// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCKADDR.
170struct SockAddrProgram(EbpfProgram<SockAddrProgram>);
171
172impl EbpfProgramContext for SockAddrProgram {
173    type RunContext<'a> = EbpfRunContextImpl<'a>;
174    type Packet<'a> = ();
175    type Arg1<'a> = &'a mut BpfSockAddr<'a>;
176    type Arg2<'a> = ();
177    type Arg3<'a> = ();
178    type Arg4<'a> = ();
179    type Arg5<'a> = ();
180
181    type Map = PinnedMap;
182}
183
184ebpf_api::ebpf_program_context_type!(SockAddrProgram, CgroupSockAddrProgramContext);
185
186#[derive(Debug, PartialEq, Eq)]
187pub enum SockAddrProgramResult {
188    Allow,
189    Block,
190}
191
192impl SockAddrProgram {
193    fn run<'a>(
194        &self,
195        current_task: &'a CurrentTask,
196        addr: &'a mut BpfSockAddr<'a>,
197        can_block: bool,
198    ) -> SockAddrProgramResult {
199        let mut run_context = EbpfRunContextImpl::new(current_task);
200        match self.0.run_with_1_argument(&mut run_context, addr) {
201            // UDP_RECVMSG programs are not allowed to block the packet.
202            0 if can_block => SockAddrProgramResult::Block,
203            1 => SockAddrProgramResult::Allow,
204            result => {
205                // TODO(https://fxbug.dev/413490751): Change this to panic once
206                // result validation is implemented in the eBPF verifier.
207                log_error!("eBPF program returned invalid result: {}", result);
208                SockAddrProgramResult::Allow
209            }
210        }
211    }
212}
213
214type AttachedSockAddrProgramCell = RcuLock<RcuArc<SockAddrProgram>, EbpfStateLock>;
215
216// Wrapper for `bpf_sock` used to implement `ProgramArgument` trait.
217#[repr(C)]
218pub struct BpfSock<'a> {
219    // Must be first field.
220    value: bpf_sock,
221
222    socket: Option<&'a ZxioBackedSocket>,
223}
224
225impl<'a> BpfSock<'a> {
226    fn from_socket(socket: &'a Socket) -> Self {
227        Self {
228            value: bpf_sock {
229                family: socket.domain.as_raw().into(),
230                type_: socket.socket_type.as_raw(),
231                protocol: socket.protocol.as_raw(),
232                state: socket.bpf_state().into(),
233                ..Default::default()
234            },
235            socket: socket.downcast_socket(),
236        }
237    }
238}
239
240impl<'a> Deref for BpfSock<'a> {
241    type Target = bpf_sock;
242    fn deref(&self) -> &Self::Target {
243        &self.value
244    }
245}
246
247impl<'a> DerefMut for BpfSock<'a> {
248    fn deref_mut(&mut self) -> &mut Self::Target {
249        &mut self.value
250    }
251}
252
253impl<'a> ProgramArgument for &'_ BpfSock<'a> {
254    fn get_type() -> &'static Type {
255        &*BPF_SOCK_TYPE
256    }
257}
258
259impl<'a> SocketRef for &'_ BpfSock<'a> {
260    fn get_socket_cookie(&self) -> Option<u64> {
261        self.socket.and_then(|socket| {
262            socket
263                .get_socket_cookie()
264                .inspect_err(|errno| log_error!("Failed to get socket cookie: {:?}", errno))
265                .ok()
266        })
267    }
268
269    fn get_socket_uid(&self) -> Option<uid_t> {
270        self.socket.map(|socket| socket.uid())
271    }
272}
273
274// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCK.
275struct SockProgram(EbpfProgram<SockProgram>);
276
277impl EbpfProgramContext for SockProgram {
278    type RunContext<'a> = EbpfRunContextImpl<'a>;
279    type Packet<'a> = ();
280    type Arg1<'a> = &'a BpfSock<'a>;
281    type Arg2<'a> = ();
282    type Arg3<'a> = ();
283    type Arg4<'a> = ();
284    type Arg5<'a> = ();
285
286    type Map = PinnedMap;
287}
288
289ebpf_api::ebpf_program_context_type!(SockProgram, CgroupSockProgramContext);
290
291#[derive(Debug, PartialEq, Eq)]
292pub enum SockProgramResult {
293    Allow,
294    Block,
295}
296
297impl SockProgram {
298    fn run<'a>(&self, current_task: &'a CurrentTask, sock: &'a BpfSock<'a>) -> SockProgramResult {
299        let mut run_context = EbpfRunContextImpl::new(current_task);
300        if self.0.run_with_1_argument(&mut run_context, sock) == 0 {
301            SockProgramResult::Block
302        } else {
303            SockProgramResult::Allow
304        }
305    }
306}
307
308type AttachedSockProgramCell = RcuLock<RcuArc<SockProgram>, EbpfStateLock>;
309
310mod internal {
311    use super::BpfSock;
312    use ebpf::{BpfValue, EbpfPtr, ProgramArgument, Type};
313    use ebpf_api::BPF_SOCKOPT_TYPE;
314    use starnix_uapi::{bpf_sockopt, uaddr};
315    use std::ops::Deref;
316    use zerocopy::{FromBytes, IntoBytes};
317
318    // Wrapper for `bpf_sockopt` that implements `ProgramArgument` trait.
319    #[repr(C)]
320    #[derive(IntoBytes, FromBytes)]
321    pub struct BpfSockOpt(bpf_sockopt);
322
323    impl ProgramArgument for &'_ mut BpfSockOpt {
324        fn get_type() -> &'static Type {
325            &*BPF_SOCKOPT_TYPE
326        }
327    }
328
329    /// Wrapper for `bpf_sockopt` that keeps a buffer for the `optval`.
330    pub struct BpfSockOptWithValue {
331        sockopt: BpfSockOpt,
332
333        // Buffer used to store the option value. A pointer to the buffer
334        // contents is stored in `sockopt`. `Vec::as_mut_ptr()` guarantees that
335        // the pointer remains valid only as long as the `Vec` is not modified,
336        // so this field should not be updated directly. `take_value()` can be
337        // used to extract the value when `BpfSockOpt` is no longer needed.
338        value_buf: Vec<u8>,
339    }
340
341    impl BpfSockOptWithValue {
342        pub fn new(
343            level: u32,
344            optname: u32,
345            value_buf: Vec<u8>,
346            optlen: u32,
347            retval: i32,
348            sock: *const BpfSock<'_>,
349        ) -> Self {
350            let mut sockopt = Self {
351                sockopt: BpfSockOpt(bpf_sockopt {
352                    level: level as i32,
353                    optname: optname as i32,
354                    optlen: optlen as i32,
355                    retval: retval as i32,
356                    ..Default::default()
357                }),
358                value_buf,
359            };
360
361            // SAFETY: Setting buffer bounds in unions is safe.
362            unsafe {
363                sockopt.sockopt.0.__bindgen_anon_2.optval =
364                    uaddr { addr: sockopt.value_buf.as_mut_ptr() as u64 };
365                sockopt.sockopt.0.__bindgen_anon_3.optval_end = uaddr {
366                    addr: sockopt.value_buf.as_mut_ptr().add(sockopt.value_buf.len()) as u64,
367                };
368            }
369
370            sockopt.sockopt.0.__bindgen_anon_1.sk =
371                (uaddr { addr: BpfValue::from(sock).into() }).into();
372
373            sockopt
374        }
375
376        pub fn as_ptr<'a>(&'a mut self) -> EbpfPtr<'a, BpfSockOpt> {
377            EbpfPtr::from(&mut self.sockopt)
378        }
379
380        // Returns the value. Consumes `self` since it's not safe to use again
381        // after the value buffer is moved.
382        pub fn take_value(self) -> Vec<u8> {
383            self.value_buf
384        }
385    }
386
387    impl Deref for BpfSockOptWithValue {
388        type Target = bpf_sockopt;
389        fn deref(&self) -> &Self::Target {
390            &self.sockopt.0
391        }
392    }
393}
394
395use internal::{BpfSockOpt, BpfSockOptWithValue};
396
397// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCKOPT.
398struct SockOptProgram(EbpfProgram<SockOptProgram>);
399
400// RunContext for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCKOPT.
401pub struct SockOptEbpfRunContextImpl<'a> {
402    ebpf_run_context: EbpfRunContextImpl<'a>,
403
404    // Pointer to the BpfSockOpt passed to the program. Used for
405    // `bpf_set_retval` and `bpf_get_retval`.
406    sockopt: EbpfPtr<'a, BpfSockOpt>,
407}
408
409const BPF_SOCKOPT_RETVAL_OFFSET: usize = std::mem::offset_of!(bpf_sockopt, retval);
410
411impl<'a> SockOptEbpfRunContextImpl<'a> {
412    pub fn new(current_task: &'a CurrentTask, sockopt: EbpfPtr<'a, BpfSockOpt>) -> Self {
413        Self { ebpf_run_context: EbpfRunContextImpl::new(current_task), sockopt }
414    }
415}
416
417impl<'a> MapsContext<'a> for SockOptEbpfRunContextImpl<'a> {
418    fn on_map_access(&mut self, map: &Map) {
419        self.ebpf_run_context.on_map_access(map);
420    }
421    fn add_value_ref(&mut self, map_ref: MapValueRef<'a>) {
422        self.ebpf_run_context.add_value_ref(map_ref);
423    }
424}
425
426impl<'a> CurrentTaskContext for SockOptEbpfRunContextImpl<'a> {
427    fn get_uid_gid(&self) -> (uid_t, gid_t) {
428        self.ebpf_run_context.get_uid_gid()
429    }
430    fn get_tid_tgid(&self) -> (pid_t, pid_t) {
431        self.ebpf_run_context.get_tid_tgid()
432    }
433}
434
435impl<'a> ReturnValueContext for SockOptEbpfRunContextImpl<'a> {
436    fn set_retval(&mut self, value: i32) -> i32 {
437        let sockopt = self.sockopt.get_field::<i32, BPF_SOCKOPT_RETVAL_OFFSET>();
438        sockopt.store_relaxed(value);
439        0
440    }
441    fn get_retval(&self) -> i32 {
442        let sockopt = self.sockopt.get_field::<i32, BPF_SOCKOPT_RETVAL_OFFSET>();
443        sockopt.load_relaxed()
444    }
445}
446
447impl<'a> BpfSockContext for SockOptEbpfRunContextImpl<'a> {
448    type BpfSockRef = &'a BpfSock<'a>;
449}
450
451impl EbpfProgramContext for SockOptProgram {
452    type RunContext<'a> = SockOptEbpfRunContextImpl<'a>;
453    type Packet<'a> = ();
454    type Arg1<'a> = EbpfPtr<'a, BpfSockOpt>;
455    type Arg2<'a> = ();
456    type Arg3<'a> = ();
457    type Arg4<'a> = ();
458    type Arg5<'a> = ();
459
460    type Map = PinnedMap;
461}
462
463ebpf_api::ebpf_program_context_type!(SockOptProgram, CgroupSockOptProgramContext);
464
465#[derive(Debug)]
466pub enum SetSockOptProgramResult {
467    /// Fail the syscall.
468    Fail(Errno),
469
470    /// Proceed with the specified option value.
471    Allow(SockOptValue),
472
473    /// Return to userspace without invoking the underlying implementation of
474    /// setsockopt.
475    Bypass,
476}
477
478impl SockOptProgram {
479    fn run<'a>(&self, current_task: &'a CurrentTask, sockopt: &'a mut BpfSockOptWithValue) -> u64 {
480        let sockopt_ptr = sockopt.as_ptr();
481        let mut run_context = SockOptEbpfRunContextImpl::new(current_task, sockopt_ptr);
482        self.0.run_with_1_argument(&mut run_context, sockopt_ptr)
483    }
484}
485
486type AttachedSockOptProgramCell = RcuLock<RcuArc<SockOptProgram>, EbpfStateLock>;
487
488#[derive(Default)]
489pub struct CgroupEbpfProgramSet {
490    inet4_bind: AttachedSockAddrProgramCell,
491    inet6_bind: AttachedSockAddrProgramCell,
492    inet4_connect: AttachedSockAddrProgramCell,
493    inet6_connect: AttachedSockAddrProgramCell,
494    udp4_sendmsg: AttachedSockAddrProgramCell,
495    udp6_sendmsg: AttachedSockAddrProgramCell,
496    udp4_recvmsg: AttachedSockAddrProgramCell,
497    udp6_recvmsg: AttachedSockAddrProgramCell,
498    sock_create: AttachedSockProgramCell,
499    sock_release: AttachedSockProgramCell,
500    set_sockopt: AttachedSockOptProgramCell,
501    get_sockopt: AttachedSockOptProgramCell,
502}
503
504#[derive(Eq, PartialEq, Debug, Copy, Clone)]
505pub enum SockAddrOp {
506    Bind,
507    Connect,
508    UdpSendMsg,
509    UdpRecvMsg,
510}
511
512#[derive(Eq, PartialEq, Debug, Copy, Clone)]
513pub enum SockOp {
514    Create,
515    Release,
516}
517
518impl CgroupEbpfProgramSet {
519    fn get_sock_addr_program(
520        &self,
521        attach_type: AttachType,
522    ) -> Result<&AttachedSockAddrProgramCell, Errno> {
523        assert!(attach_type.is_cgroup());
524
525        match attach_type {
526            AttachType::CgroupInet4Bind => Ok(&self.inet4_bind),
527            AttachType::CgroupInet6Bind => Ok(&self.inet6_bind),
528            AttachType::CgroupInet4Connect => Ok(&self.inet4_connect),
529            AttachType::CgroupInet6Connect => Ok(&self.inet6_connect),
530            AttachType::CgroupUdp4Sendmsg => Ok(&self.udp4_sendmsg),
531            AttachType::CgroupUdp6Sendmsg => Ok(&self.udp6_sendmsg),
532            AttachType::CgroupUdp4Recvmsg => Ok(&self.udp4_recvmsg),
533            AttachType::CgroupUdp6Recvmsg => Ok(&self.udp6_recvmsg),
534            _ => error!(ENOTSUP),
535        }
536    }
537
538    fn get_sock_program(&self, attach_type: AttachType) -> Result<&AttachedSockProgramCell, Errno> {
539        assert!(attach_type.is_cgroup());
540
541        match attach_type {
542            AttachType::CgroupInetSockCreate => Ok(&self.sock_create),
543            AttachType::CgroupInetSockRelease => Ok(&self.sock_release),
544            _ => error!(ENOTSUP),
545        }
546    }
547
548    fn get_sock_opt_program(
549        &self,
550        attach_type: AttachType,
551    ) -> Result<&AttachedSockOptProgramCell, Errno> {
552        assert!(attach_type.is_cgroup());
553
554        match attach_type {
555            AttachType::CgroupSetsockopt => Ok(&self.set_sockopt),
556            AttachType::CgroupGetsockopt => Ok(&self.get_sockopt),
557            _ => error!(ENOTSUP),
558        }
559    }
560
561    // Executes eBPF program for the operation `op`. `socket_address` contains
562    // socket address as a `sockaddr` struct.
563    pub fn run_sock_addr_prog(
564        &self,
565        current_task: &CurrentTask,
566        op: SockAddrOp,
567        domain: SocketDomain,
568        socket_type: SocketType,
569        protocol: SocketProtocol,
570        socket_address: &[u8],
571        socket: &Socket,
572    ) -> Result<SockAddrProgramResult, Errno> {
573        let prog_cell = match (domain, op) {
574            (SocketDomain::Inet, SockAddrOp::Bind) => &self.inet4_bind,
575            (SocketDomain::Inet6, SockAddrOp::Bind) => &self.inet6_bind,
576            (SocketDomain::Inet, SockAddrOp::Connect) => &self.inet4_connect,
577            (SocketDomain::Inet6, SockAddrOp::Connect) => &self.inet6_connect,
578            (SocketDomain::Inet, SockAddrOp::UdpSendMsg) => &self.udp4_sendmsg,
579            (SocketDomain::Inet6, SockAddrOp::UdpSendMsg) => &self.udp6_sendmsg,
580            (SocketDomain::Inet, SockAddrOp::UdpRecvMsg) => &self.udp4_recvmsg,
581            (SocketDomain::Inet6, SockAddrOp::UdpRecvMsg) => &self.udp6_recvmsg,
582            _ => return Ok(SockAddrProgramResult::Allow),
583        };
584
585        let Some(prog) = prog_cell.upgrade() else {
586            return Ok(SockAddrProgramResult::Allow);
587        };
588
589        let bpf_sock = BpfSock::from_socket(socket);
590
591        let mut bpf_sockaddr = BpfSockAddr { sock_addr: Default::default(), bpf_sock: &bpf_sock };
592        bpf_sockaddr.family = domain.as_raw().into();
593        bpf_sockaddr.type_ = socket_type.as_raw();
594        bpf_sockaddr.protocol = protocol.as_raw();
595
596        let (sa_family, _) = u16::read_from_prefix(socket_address).map_err(|_| errno!(EINVAL))?;
597
598        if domain.as_raw() != sa_family {
599            return error!(EAFNOSUPPORT);
600        }
601        bpf_sockaddr.user_family = sa_family.into();
602
603        match sa_family.into() {
604            linux_uapi::AF_INET => {
605                let (sockaddr, _) = linux_uapi::sockaddr_in::ref_from_prefix(socket_address)
606                    .map_err(|_| errno!(EINVAL))?;
607                bpf_sockaddr.user_port = sockaddr.sin_port.into();
608                bpf_sockaddr.user_ip4 = sockaddr.sin_addr.s_addr;
609            }
610            linux_uapi::AF_INET6 => {
611                let sockaddr = linux_uapi::sockaddr_in6::ref_from_prefix(socket_address)
612                    .map_err(|_| errno!(EINVAL))?
613                    .0;
614                bpf_sockaddr.user_port = sockaddr.sin6_port.into();
615                // SAFETY: reading an array of u32 from a union is safe.
616                bpf_sockaddr.user_ip6 = unsafe { sockaddr.sin6_addr.in6_u.u6_addr32 };
617            }
618            _ => return error!(EAFNOSUPPORT),
619        }
620
621        bpf_sockaddr.__bindgen_anon_1.sk =
622            (uaddr { addr: BpfValue::from(&bpf_sock).into() }).into();
623
624        // UDP recvmsg programs are not allowed to filter packets.
625        let can_block = op != SockAddrOp::UdpRecvMsg;
626        Ok(prog.run(current_task, &mut bpf_sockaddr, can_block))
627    }
628
629    pub fn run_sock_prog(
630        &self,
631        current_task: &CurrentTask,
632        op: SockOp,
633        domain: SocketDomain,
634        socket_type: SocketType,
635        protocol: SocketProtocol,
636        socket: &ZxioBackedSocket,
637    ) -> SockProgramResult {
638        let prog_cell = match op {
639            SockOp::Create => &self.sock_create,
640            SockOp::Release => &self.sock_release,
641        };
642        let Some(prog) = prog_cell.upgrade() else {
643            return SockProgramResult::Allow;
644        };
645
646        let bpf_sock = BpfSock {
647            value: bpf_sock {
648                family: domain.as_raw().into(),
649                type_: socket_type.as_raw(),
650                protocol: protocol.as_raw(),
651                state: SocketBpfState::Close.into(),
652                ..Default::default()
653            },
654            socket: Some(socket),
655        };
656
657        prog.run(current_task, &bpf_sock)
658    }
659
660    pub fn run_getsockopt_prog(
661        &self,
662        current_task: &CurrentTask,
663        level: u32,
664        optname: u32,
665        value_buf: Vec<u8>,
666        optlen: usize,
667        error: Option<Errno>,
668        socket: &Socket,
669    ) -> Result<(Vec<u8>, usize), Errno> {
670        let Some(prog) = self.get_sockopt.upgrade() else {
671            return error.map(|e| Err(e)).unwrap_or_else(|| Ok((value_buf, optlen)));
672        };
673
674        let bpf_sock = BpfSock::from_socket(socket);
675
676        let retval = error.as_ref().map(|e| -(e.code.error_code() as i32)).unwrap_or(0);
677        let mut bpf_sockopt = BpfSockOptWithValue::new(
678            level,
679            optname,
680            value_buf.clone(),
681            optlen as u32,
682            retval,
683            &bpf_sock,
684        );
685
686        // Run the program.
687        let result = prog.run(current_task, &mut bpf_sockopt);
688
689        let retval = bpf_sockopt.retval;
690
691        let retval = match result {
692            0 if is_error_return_value(retval) => retval,
693            0 => -(linux_uapi::EPERM as i32),
694            1 => retval,
695            _ => {
696                // TODO(https://fxbug.dev/413490751): Change this to panic once
697                // result validation is implemented in the verifier.
698                log_error!("eBPF getsockopt program returned invalid result: {}", result);
699                retval
700            }
701        };
702
703        if retval < 0 {
704            return Err(Errno::new(ErrnoCode::from_error_code(-retval as i16)));
705        }
706
707        let new_optlen = bpf_sockopt.optlen;
708
709        match usize::try_from(new_optlen) {
710            // Fail if the program set an invalid `optlen`.
711            Err(_) => error!(EFAULT),
712            Ok(new_optlen) if new_optlen > value_buf.len() => error!(EFAULT),
713
714            // If `optlen` is set to 0 then proceed with the original value.
715            Ok(0) => Ok((value_buf, optlen)),
716
717            Ok(new_optlen) => Ok((bpf_sockopt.take_value(), new_optlen)),
718        }
719    }
720
721    pub fn run_setsockopt_prog(
722        &self,
723        current_task: &CurrentTask,
724        level: u32,
725        optname: u32,
726        value: SockOptValue,
727        socket: &Socket,
728    ) -> SetSockOptProgramResult {
729        let Some(prog) = self.set_sockopt.upgrade() else {
730            return SetSockOptProgramResult::Allow(value);
731        };
732
733        let page_size = *PAGE_SIZE as usize;
734
735        // Read only the first page from the user-specified buffer in case it's
736        // larger than that.
737        let buffer = match value.read_bytes(current_task, page_size) {
738            Ok(buffer) => buffer,
739            Err(err) => return SetSockOptProgramResult::Fail(err),
740        };
741
742        let bpf_sock = BpfSock::from_socket(socket);
743
744        let buffer_len = buffer.len();
745        let optlen = value.len();
746        let mut bpf_sockopt =
747            BpfSockOptWithValue::new(level, optname, buffer, optlen as u32, 0, &bpf_sock);
748        let result = prog.run(current_task, &mut bpf_sockopt);
749
750        let retval = bpf_sockopt.retval;
751
752        let retval = match result {
753            0 if is_error_return_value(retval) => retval,
754            0 => -(linux_uapi::EPERM as i32),
755            1 => retval,
756            _ => {
757                // TODO(https://fxbug.dev/413490751): Change this to panic once
758                // result validation is implemented in the verifier.
759                log_error!("eBPF getsockopt program returned invalid result: {}", result);
760                retval
761            }
762        };
763
764        if retval < 0 {
765            return SetSockOptProgramResult::Fail(Errno::new(ErrnoCode::from_error_code(
766                -retval as i16,
767            )));
768        }
769
770        match bpf_sockopt.optlen {
771            // `setsockopt` programs can bypass the platform implementation by
772            // setting `optlen` to -1.
773            -1 => SetSockOptProgramResult::Bypass,
774
775            // If the original value is larger than a page and the program
776            // didn't change `optlen` then return the original value. This
777            // allows to avoid `EFAULT` below with a no-op program.
778            new_optlen if optlen > page_size && (new_optlen as usize) == optlen => {
779                SetSockOptProgramResult::Allow(value)
780            }
781
782            // Fail if the program has set an invalid `optlen` (except for the
783            // case handled above).
784            optlen if optlen < 0 || (optlen as usize) > buffer_len => {
785                SetSockOptProgramResult::Fail(errno!(EFAULT))
786            }
787
788            // If `optlen` is set to 0 then proceed with the original value.
789            0 => SetSockOptProgramResult::Allow(value),
790
791            // Return value from `bpf_sockbuf` - it may be different from the
792            // original value.
793            optlen => {
794                let mut value = bpf_sockopt.take_value();
795                value.resize(optlen as usize, 0);
796                SetSockOptProgramResult::Allow(value.into())
797            }
798        }
799    }
800}
801
802fn attach_type_to_netstack_hook(attach_type: AttachType) -> Option<fnet_filter::SocketHook> {
803    let hook = match attach_type {
804        AttachType::CgroupInetEgress => fnet_filter::SocketHook::Egress,
805        AttachType::CgroupInetIngress => fnet_filter::SocketHook::Ingress,
806        _ => return None,
807    };
808    Some(hook)
809}
810
811// Defined a location where eBPF programs can be attached.
812#[derive(Copy, Clone, Debug, PartialEq, Eq)]
813enum AttachLocation {
814    // Attached in Starnix kernel.
815    Kernel,
816
817    // Attached in Netstack.
818    Netstack,
819}
820
821impl TryFrom<AttachType> for AttachLocation {
822    type Error = Errno;
823
824    fn try_from(attach_type: AttachType) -> Result<Self, Self::Error> {
825        match attach_type {
826            AttachType::CgroupInet4Bind
827            | AttachType::CgroupInet6Bind
828            | AttachType::CgroupInet4Connect
829            | AttachType::CgroupInet6Connect
830            | AttachType::CgroupUdp4Sendmsg
831            | AttachType::CgroupUdp6Sendmsg
832            | AttachType::CgroupUdp4Recvmsg
833            | AttachType::CgroupUdp6Recvmsg
834            | AttachType::CgroupInetSockCreate
835            | AttachType::CgroupInetSockRelease
836            | AttachType::CgroupGetsockopt
837            | AttachType::CgroupSetsockopt => Ok(AttachLocation::Kernel),
838
839            AttachType::CgroupInetEgress | AttachType::CgroupInetIngress => {
840                Ok(AttachLocation::Netstack)
841            }
842
843            AttachType::CgroupDevice
844            | AttachType::CgroupInet4Getpeername
845            | AttachType::CgroupInet4Getsockname
846            | AttachType::CgroupInet4PostBind
847            | AttachType::CgroupInet6Getpeername
848            | AttachType::CgroupInet6Getsockname
849            | AttachType::CgroupInet6PostBind
850            | AttachType::CgroupSysctl
851            | AttachType::CgroupUnixConnect
852            | AttachType::CgroupUnixGetpeername
853            | AttachType::CgroupUnixGetsockname
854            | AttachType::CgroupUnixRecvmsg
855            | AttachType::CgroupUnixSendmsg
856            | AttachType::CgroupSockOps
857            | AttachType::SkSkbStreamParser
858            | AttachType::SkSkbStreamVerdict
859            | AttachType::SkMsgVerdict
860            | AttachType::LircMode2
861            | AttachType::FlowDissector
862            | AttachType::TraceRawTp
863            | AttachType::TraceFentry
864            | AttachType::TraceFexit
865            | AttachType::ModifyReturn
866            | AttachType::LsmMac
867            | AttachType::TraceIter
868            | AttachType::XdpDevmap
869            | AttachType::XdpCpumap
870            | AttachType::SkLookup
871            | AttachType::Xdp
872            | AttachType::SkSkbVerdict
873            | AttachType::SkReuseportSelect
874            | AttachType::SkReuseportSelectOrMigrate
875            | AttachType::PerfEvent
876            | AttachType::TraceKprobeMulti
877            | AttachType::LsmCgroup
878            | AttachType::StructOps
879            | AttachType::Netfilter
880            | AttachType::TcxIngress
881            | AttachType::TcxEgress
882            | AttachType::TraceUprobeMulti
883            | AttachType::NetkitPrimary
884            | AttachType::NetkitPeer
885            | AttachType::TraceKprobeSession => {
886                track_stub!(TODO("https://fxbug.dev/322873416"), "BPF_PROG_ATTACH", attach_type);
887                error!(ENOTSUP)
888            }
889
890            AttachType::Unspecified | AttachType::Invalid(_) => {
891                error!(EINVAL)
892            }
893        }
894    }
895}
896
897fn get_capability_for_program(program_type: ProgramType) -> Result<Capabilities, Errno> {
898    match program_type {
899        ProgramType::CgroupSkb
900        | ProgramType::CgroupSock
901        | ProgramType::CgroupSockAddr
902        | ProgramType::CgroupSockopt
903        | ProgramType::CgroupSysctl => Ok(CAP_NET_ADMIN),
904
905        // The following program types cannot be attached with
906        // `bpf(BPF_PROG_ATTACH)` yet.
907        ProgramType::CgroupDevice
908        | ProgramType::Ext
909        | ProgramType::FlowDissector
910        | ProgramType::Kprobe
911        | ProgramType::LircMode2
912        | ProgramType::Lsm
913        | ProgramType::LwtIn
914        | ProgramType::LwtOut
915        | ProgramType::LwtSeg6Local
916        | ProgramType::LwtXmit
917        | ProgramType::Netfilter
918        | ProgramType::PerfEvent
919        | ProgramType::RawTracepoint
920        | ProgramType::RawTracepointWritable
921        | ProgramType::SchedAct
922        | ProgramType::SchedCls
923        | ProgramType::SkLookup
924        | ProgramType::SkMsg
925        | ProgramType::SkReuseport
926        | ProgramType::SkSkb
927        | ProgramType::SocketFilter
928        | ProgramType::SockOps
929        | ProgramType::StructOps
930        | ProgramType::Syscall
931        | ProgramType::Tracepoint
932        | ProgramType::Tracing
933        | ProgramType::Unspec
934        | ProgramType::Xdp => error!(ENOTSUP),
935    }
936}
937
938#[derive(Default)]
939pub struct EbpfAttachments {
940    root_cgroup: CgroupEbpfProgramSet,
941    socket_control: OnceLock<fnet_filter::SocketControlSynchronousProxy>,
942}
943
944impl EbpfAttachments {
945    pub fn root_cgroup(&self) -> &CgroupEbpfProgramSet {
946        &self.root_cgroup
947    }
948
949    fn socket_control(&self) -> &fnet_filter::SocketControlSynchronousProxy {
950        self.socket_control.get_or_init(|| {
951            connect_to_protocol_sync::<fnet_filter::SocketControlMarker>()
952                .expect("Failed to connect to fuchsia.net.filter.SocketControl.")
953        })
954    }
955
956    fn attach_prog(
957        &self,
958        current_task: &CurrentTask,
959        attach_type: AttachType,
960        target_fd: FdNumber,
961        program: ProgramHandle,
962    ) -> Result<SyscallResult, Errno> {
963        let location: AttachLocation = attach_type.try_into()?;
964        let program_type = attach_type.get_program_type();
965        match (location, program_type) {
966            (AttachLocation::Kernel, ProgramType::CgroupSockAddr) => {
967                check_root_cgroup_fd(current_task, target_fd)?;
968
969                let linked_program = SockAddrProgram(program.link(attach_type.get_program_type())?);
970                self.root_cgroup
971                    .get_sock_addr_program(attach_type)?
972                    .lock()
973                    .update(Some(Arc::new(linked_program)));
974
975                Ok(SUCCESS)
976            }
977
978            (AttachLocation::Kernel, ProgramType::CgroupSock) => {
979                check_root_cgroup_fd(current_task, target_fd)?;
980
981                let linked_program = SockProgram(program.link(attach_type.get_program_type())?);
982                self.root_cgroup
983                    .get_sock_program(attach_type)?
984                    .lock()
985                    .update(Some(Arc::new(linked_program)));
986
987                Ok(SUCCESS)
988            }
989
990            (AttachLocation::Kernel, ProgramType::CgroupSockopt) => {
991                check_root_cgroup_fd(current_task, target_fd)?;
992
993                let linked_program = SockOptProgram(program.link(attach_type.get_program_type())?);
994                self.root_cgroup
995                    .get_sock_opt_program(attach_type)?
996                    .lock()
997                    .update(Some(Arc::new(linked_program)));
998
999                Ok(SUCCESS)
1000            }
1001
1002            (AttachLocation::Kernel, _) => {
1003                unreachable!();
1004            }
1005
1006            (AttachLocation::Netstack, _) => {
1007                check_root_cgroup_fd(current_task, target_fd)?;
1008                self.attach_prog_in_netstack(attach_type, program)
1009            }
1010        }
1011    }
1012
1013    fn detach_prog(
1014        &self,
1015        current_task: &CurrentTask,
1016        attach_type: AttachType,
1017        target_fd: FdNumber,
1018    ) -> Result<SyscallResult, Errno> {
1019        let location = attach_type.try_into()?;
1020        let program_type = attach_type.get_program_type();
1021        match (location, program_type) {
1022            (AttachLocation::Kernel, ProgramType::CgroupSockAddr) => {
1023                check_root_cgroup_fd(current_task, target_fd)?;
1024
1025                let prog_cell = self.root_cgroup.get_sock_addr_program(attach_type)?.lock();
1026                if prog_cell.is_none() {
1027                    return error!(ENOENT);
1028                }
1029
1030                prog_cell.update(None);
1031
1032                Ok(SUCCESS)
1033            }
1034
1035            (AttachLocation::Kernel, ProgramType::CgroupSock) => {
1036                check_root_cgroup_fd(current_task, target_fd)?;
1037
1038                let prog_cell = self.root_cgroup.get_sock_program(attach_type)?.lock();
1039                if prog_cell.is_none() {
1040                    return error!(ENOENT);
1041                }
1042
1043                prog_cell.update(None);
1044
1045                Ok(SUCCESS)
1046            }
1047
1048            (AttachLocation::Kernel, ProgramType::CgroupSockopt) => {
1049                check_root_cgroup_fd(current_task, target_fd)?;
1050
1051                let prog_cell = self.root_cgroup.get_sock_opt_program(attach_type)?.lock();
1052                if prog_cell.is_none() {
1053                    return error!(ENOENT);
1054                }
1055
1056                prog_cell.update(None);
1057
1058                Ok(SUCCESS)
1059            }
1060
1061            (AttachLocation::Kernel, _) => {
1062                unreachable!();
1063            }
1064
1065            (AttachLocation::Netstack, _) => {
1066                check_root_cgroup_fd(current_task, target_fd)?;
1067                self.detach_prog_in_netstack(attach_type)
1068            }
1069        }
1070    }
1071
1072    fn attach_prog_in_netstack(
1073        &self,
1074        attach_type: AttachType,
1075        program: ProgramHandle,
1076    ) -> Result<SyscallResult, Errno> {
1077        let hook = attach_type_to_netstack_hook(attach_type).ok_or_else(|| errno!(ENOTSUP))?;
1078        let opts = fnet_filter::AttachEbpfProgramOptions {
1079            hook: Some(hook),
1080            program: Some((&**program).try_into()?),
1081            ..Default::default()
1082        };
1083        self.socket_control()
1084            .attach_ebpf_program(opts, zx::MonotonicInstant::INFINITE)
1085            .map_err(|e| {
1086                log_error!(
1087                    "failed to send fuchsia.net.filter/SocketControl.AttachEbpfProgram: {}",
1088                    e
1089                );
1090                errno!(EIO)
1091            })?
1092            .map_err(|e| {
1093                use fnet_filter::SocketControlAttachEbpfProgramError as Error;
1094                match e {
1095                    Error::NotSupported => errno!(ENOTSUP),
1096                    Error::LinkFailed => errno!(EINVAL),
1097                    Error::MapFailed => errno!(EIO),
1098                    Error::DuplicateAttachment => errno!(EEXIST),
1099                }
1100            })?;
1101
1102        Ok(SUCCESS)
1103    }
1104
1105    fn detach_prog_in_netstack(&self, attach_type: AttachType) -> Result<SyscallResult, Errno> {
1106        let hook = attach_type_to_netstack_hook(attach_type).ok_or_else(|| errno!(ENOTSUP))?;
1107        self.socket_control()
1108            .detach_ebpf_program(hook, zx::MonotonicInstant::INFINITE)
1109            .map_err(|e| {
1110                log_error!(
1111                    "failed to send fuchsia.net.filter/SocketControl.DetachEbpfProgram: {}",
1112                    e
1113                );
1114                errno!(EIO)
1115            })?
1116            .map_err(|e| {
1117                use fnet_filter::SocketControlDetachEbpfProgramError as Error;
1118                match e {
1119                    Error::NotFound => errno!(ENOENT),
1120                }
1121            })?;
1122        Ok(SUCCESS)
1123    }
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128    use super::*;
1129    use crate::bpf::program::{Program, ProgramInfo};
1130    use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
1131    use crate::testing::{PanickingFile, PanickingFsNode, spawn_kernel_and_run};
1132    use crate::vfs::buffers::VecOutputBuffer;
1133    use crate::vfs::{
1134        CacheMode, FdFlags, FileObject, FileSystem, FileSystemOps, FileSystemOptions, FsStr,
1135    };
1136    use ebpf::{
1137        EbpfInstruction, MapFlags, MapSchema, NullVerifierLogger, link_program, verify_program,
1138    };
1139    use linux_uapi::{
1140        AF_INET, BPF_ALU64, BPF_EXIT, BPF_JMP, BPF_K, BPF_MOV, bpf_map_type_BPF_MAP_TYPE_ARRAY,
1141        bpf_map_type_BPF_MAP_TYPE_RINGBUF, sockaddr_in,
1142    };
1143    use starnix_types::vfs::default_statfs;
1144    use starnix_uapi::open_flags::OpenFlags;
1145    use starnix_uapi::statfs;
1146    use std::sync::atomic::{AtomicBool, Ordering};
1147    use syncio::Zxio;
1148    use zerocopy::IntoBytes as _;
1149
1150    const STRESS_WRITER_ITERATIONS: i32 = 50;
1151    const STRESS_READER_THREADS: usize = 2;
1152
1153    struct TestCgroup2Fs;
1154    impl FileSystemOps for TestCgroup2Fs {
1155        fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
1156            Ok(default_statfs(CGROUP2_SUPER_MAGIC))
1157        }
1158        fn name(&self) -> &'static FsStr {
1159            "cgroup2".into()
1160        }
1161    }
1162
1163    fn create_root_cgroup_fd(current_task: &CurrentTask) -> FdNumber {
1164        let fs = FileSystem::new(
1165            current_task.kernel(),
1166            CacheMode::Uncached,
1167            TestCgroup2Fs,
1168            FileSystemOptions::default(),
1169        )
1170        .expect("create cgroup2 filesystem");
1171        let root_ino = fs.allocate_ino();
1172        fs.create_root(root_ino, PanickingFsNode);
1173        let root_node = fs.root().node.clone();
1174        let file = FileObject::new_anonymous(
1175            current_task,
1176            Box::new(PanickingFile),
1177            root_node,
1178            OpenFlags::RDONLY,
1179        );
1180        current_task.add_file(file, FdFlags::empty()).expect("add root cgroup fd")
1181    }
1182
1183    fn make_cgroup_program(
1184        current_task: &CurrentTask,
1185        program_type: ProgramType,
1186        expected_attach_type: AttachType,
1187        return_value: i32,
1188    ) -> ProgramHandle {
1189        let info =
1190            ProgramInfo { program_type, expected_attach_type, log_level: Default::default() };
1191        let code = vec![
1192            EbpfInstruction::new((BPF_ALU64 | BPF_MOV | BPF_K) as u8, 0, 0, 0, return_value),
1193            EbpfInstruction::new((BPF_JMP | BPF_EXIT) as u8, 0, 0, 0, 0),
1194        ];
1195        let mut logger = VecOutputBuffer::new(0);
1196        Program::new(current_task, info, &mut logger, code).expect("create eBPF program")
1197    }
1198
1199    #[::fuchsia::test]
1200    async fn test_cgroup_ebpf_attach_detach_and_vmar_teardown() {
1201        spawn_kernel_and_run(async |current_task| {
1202            let ebpf_state = &current_task.kernel().ebpf_state;
1203            let root_cgroup = ebpf_state.attachments.root_cgroup();
1204
1205            // Verify all 12 cgroup attachment slots start unattached.
1206            assert!(root_cgroup.inet4_bind.is_none());
1207            assert!(root_cgroup.inet6_bind.is_none());
1208            assert!(root_cgroup.inet4_connect.is_none());
1209            assert!(root_cgroup.inet6_connect.is_none());
1210            assert!(root_cgroup.udp4_sendmsg.is_none());
1211            assert!(root_cgroup.udp6_sendmsg.is_none());
1212            assert!(root_cgroup.udp4_recvmsg.is_none());
1213            assert!(root_cgroup.udp6_recvmsg.is_none());
1214            assert!(root_cgroup.sock_create.is_none());
1215            assert!(root_cgroup.sock_release.is_none());
1216            assert!(root_cgroup.set_sockopt.is_none());
1217            assert!(root_cgroup.get_sockopt.is_none());
1218
1219            let cgroup_fd = create_root_cgroup_fd(current_task);
1220            let zxio = Zxio::create(zx::Event::create().into()).expect("create zxio");
1221            let zxio_socket = ZxioBackedSocket::new_with_zxio(current_task, zxio);
1222            let unix_socket = Socket::new(
1223                current_task,
1224                SocketDomain::Unix,
1225                SocketType::Datagram,
1226                SocketProtocol::default(),
1227                true,
1228            )
1229            .expect("create socket");
1230            let inet4_sockaddr = sockaddr_in { sin_family: AF_INET as u16, ..Default::default() };
1231
1232            // Unattached hooks take the lockless is_none() fast path.
1233            assert_eq!(
1234                root_cgroup.run_sock_prog(
1235                    current_task,
1236                    SockOp::Create,
1237                    SocketDomain::Inet,
1238                    SocketType::Stream,
1239                    SocketProtocol::TCP,
1240                    &zxio_socket,
1241                ),
1242                SockProgramResult::Allow
1243            );
1244            assert_eq!(
1245                root_cgroup.run_sock_addr_prog(
1246                    current_task,
1247                    SockAddrOp::Bind,
1248                    SocketDomain::Inet,
1249                    SocketType::Stream,
1250                    SocketProtocol::TCP,
1251                    inet4_sockaddr.as_bytes(),
1252                    &unix_socket,
1253                ),
1254                Ok(SockAddrProgramResult::Allow)
1255            );
1256            assert_eq!(
1257                root_cgroup.run_getsockopt_prog(
1258                    current_task,
1259                    0,
1260                    0,
1261                    vec![1, 2, 3, 4],
1262                    4,
1263                    None,
1264                    &unix_socket,
1265                ),
1266                Ok((vec![1, 2, 3, 4], 4))
1267            );
1268            assert!(matches!(
1269                root_cgroup.run_setsockopt_prog(
1270                    current_task,
1271                    0,
1272                    0,
1273                    vec![1, 2, 3, 4].into(),
1274                    &unix_socket,
1275                ),
1276                SetSockOptProgramResult::Allow(_)
1277            ));
1278
1279            // Detaching an unattached slot returns ENOENT.
1280            assert_eq!(
1281                ebpf_state.attachments.detach_prog(
1282                    current_task,
1283                    AttachType::CgroupInetSockCreate,
1284                    cgroup_fd,
1285                ),
1286                error!(ENOENT)
1287            );
1288
1289            // Attach a program holding both a VMAR-backed ringbuf map (AllocatedVmar) and an
1290            // array map (MapBuffer) to verify synchronous writer-thread drop on replacement.
1291            let ringbuf_map = Map::new(
1292                MapSchema {
1293                    map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
1294                    key_size: 0,
1295                    value_size: 0,
1296                    max_entries: *PAGE_SIZE as u32,
1297                    flags: MapFlags::empty(),
1298                },
1299                "test_ringbuf_vmar",
1300            )
1301            .expect("create ringbuf map with AllocatedVmar");
1302            let array_map = Map::new(
1303                MapSchema {
1304                    map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
1305                    key_size: 4,
1306                    value_size: 8,
1307                    max_entries: 1,
1308                    flags: MapFlags::empty(),
1309                },
1310                "test_array_map",
1311            )
1312            .expect("create array map");
1313            let code = vec![
1314                EbpfInstruction::new((BPF_ALU64 | BPF_MOV | BPF_K) as u8, 0, 0, 0, 0),
1315                EbpfInstruction::new((BPF_JMP | BPF_EXIT) as u8, 0, 0, 0, 0),
1316            ];
1317            let calling_context = ProgramType::CgroupSock
1318                .create_calling_context(
1319                    AttachType::CgroupInetSockCreate,
1320                    vec![ringbuf_map.schema, array_map.schema],
1321                )
1322                .expect("calling context");
1323            let verified = verify_program(code, calling_context, &mut NullVerifierLogger)
1324                .expect("verify program");
1325            let linked =
1326                link_program::<SockProgram>(&verified, vec![ringbuf_map, array_map]).expect("link");
1327            root_cgroup
1328                .get_sock_program(AttachType::CgroupInetSockCreate)
1329                .expect("get sock_create slot")
1330                .lock()
1331                .update(Some(Arc::new(SockProgram(linked))));
1332
1333            assert_eq!(
1334                root_cgroup.run_sock_prog(
1335                    current_task,
1336                    SockOp::Create,
1337                    SocketDomain::Inet,
1338                    SocketType::Stream,
1339                    SocketProtocol::TCP,
1340                    &zxio_socket,
1341                ),
1342                SockProgramResult::Block
1343            );
1344
1345            // Replace the program via attach_prog; the old SockProgram (and its AllocatedVmar)
1346            // drops synchronously on the writer thread.
1347            let allow_prog = make_cgroup_program(
1348                current_task,
1349                ProgramType::CgroupSock,
1350                AttachType::CgroupInetSockCreate,
1351                1,
1352            );
1353            ebpf_state
1354                .attachments
1355                .attach_prog(current_task, AttachType::CgroupInetSockCreate, cgroup_fd, allow_prog)
1356                .expect("attach allow program");
1357            assert_eq!(
1358                root_cgroup.run_sock_prog(
1359                    current_task,
1360                    SockOp::Create,
1361                    SocketDomain::Inet,
1362                    SocketType::Stream,
1363                    SocketProtocol::TCP,
1364                    &zxio_socket,
1365                ),
1366                SockProgramResult::Allow
1367            );
1368
1369            ebpf_state
1370                .attachments
1371                .detach_prog(current_task, AttachType::CgroupInetSockCreate, cgroup_fd)
1372                .expect("detach program");
1373            assert!(root_cgroup.sock_create.is_none());
1374            current_task.trigger_delayed_releaser();
1375            fuchsia_rcu::rcu_run_callbacks();
1376        })
1377        .await;
1378    }
1379
1380    #[::fuchsia::test]
1381    async fn test_concurrent_cgroup_ebpf_attach_detach_and_execution() {
1382        spawn_kernel_and_run(async |current_task| {
1383            let kernel = current_task.kernel();
1384            let cgroup_fd = create_root_cgroup_fd(current_task);
1385            let unix_socket = Socket::new(
1386                current_task,
1387                SocketDomain::Unix,
1388                SocketType::Datagram,
1389                SocketProtocol::default(),
1390                true,
1391            )
1392            .expect("create socket");
1393            let done = Arc::new(AtomicBool::new(false));
1394
1395            let mut reader_results = Vec::new();
1396            for _ in 0..STRESS_READER_THREADS {
1397                let socket_clone = unix_socket.clone();
1398                let done_clone = done.clone();
1399                let (res_fn, req) = SpawnRequestBuilder::new()
1400                    .with_sync_closure(move |task: &CurrentTask| {
1401                        let zxio =
1402                            Zxio::create(zx::Event::create().into()).expect("create reader zxio");
1403                        let zxio_socket = ZxioBackedSocket::new_with_zxio(task, zxio);
1404                        let inet4_sockaddr =
1405                            sockaddr_in { sin_family: AF_INET as u16, ..Default::default() };
1406                        let cgroup = task.kernel().ebpf_state.attachments.root_cgroup();
1407                        while !done_clone.load(Ordering::Relaxed) {
1408                            let sock_res = cgroup.run_sock_prog(
1409                                task,
1410                                SockOp::Create,
1411                                SocketDomain::Inet,
1412                                SocketType::Stream,
1413                                SocketProtocol::TCP,
1414                                &zxio_socket,
1415                            );
1416                            assert!(matches!(
1417                                sock_res,
1418                                SockProgramResult::Allow | SockProgramResult::Block
1419                            ));
1420
1421                            let addr_res = cgroup
1422                                .run_sock_addr_prog(
1423                                    task,
1424                                    SockAddrOp::Bind,
1425                                    SocketDomain::Inet,
1426                                    SocketType::Stream,
1427                                    SocketProtocol::TCP,
1428                                    inet4_sockaddr.as_bytes(),
1429                                    &socket_clone,
1430                                )
1431                                .expect("run_sock_addr_prog");
1432                            assert!(matches!(
1433                                addr_res,
1434                                SockAddrProgramResult::Allow | SockAddrProgramResult::Block
1435                            ));
1436                        }
1437                    })
1438                    .build_with_sync_result();
1439                kernel.kthreads.spawner().spawn_from_request(req);
1440                reader_results.push(res_fn);
1441            }
1442
1443            for iteration in 0..STRESS_WRITER_ITERATIONS {
1444                let sock_prog = make_cgroup_program(
1445                    current_task,
1446                    ProgramType::CgroupSock,
1447                    AttachType::CgroupInetSockCreate,
1448                    iteration % 2,
1449                );
1450                kernel
1451                    .ebpf_state
1452                    .attachments
1453                    .attach_prog(
1454                        current_task,
1455                        AttachType::CgroupInetSockCreate,
1456                        cgroup_fd,
1457                        sock_prog,
1458                    )
1459                    .expect("attach sock_create");
1460
1461                let addr_prog = make_cgroup_program(
1462                    current_task,
1463                    ProgramType::CgroupSockAddr,
1464                    AttachType::CgroupInet4Bind,
1465                    iteration % 2,
1466                );
1467                kernel
1468                    .ebpf_state
1469                    .attachments
1470                    .attach_prog(current_task, AttachType::CgroupInet4Bind, cgroup_fd, addr_prog)
1471                    .expect("attach inet4_bind");
1472
1473                if iteration % 3 == 0 {
1474                    kernel
1475                        .ebpf_state
1476                        .attachments
1477                        .detach_prog(current_task, AttachType::CgroupInetSockCreate, cgroup_fd)
1478                        .expect("detach sock_create");
1479                    kernel
1480                        .ebpf_state
1481                        .attachments
1482                        .detach_prog(current_task, AttachType::CgroupInet4Bind, cgroup_fd)
1483                        .expect("detach inet4_bind");
1484                }
1485                current_task.trigger_delayed_releaser();
1486                fuchsia_rcu::rcu_run_callbacks();
1487            }
1488
1489            done.store(true, Ordering::Relaxed);
1490            for res_fn in reader_results {
1491                res_fn().expect("reader thread completed");
1492            }
1493            let _ = kernel.ebpf_state.attachments.detach_prog(
1494                current_task,
1495                AttachType::CgroupInetSockCreate,
1496                cgroup_fd,
1497            );
1498            let _ = kernel.ebpf_state.attachments.detach_prog(
1499                current_task,
1500                AttachType::CgroupInet4Bind,
1501                cgroup_fd,
1502            );
1503            current_task.trigger_delayed_releaser();
1504            fuchsia_rcu::rcu_run_callbacks();
1505        })
1506        .await;
1507    }
1508}