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::context::EbpfRunContextImpl;
9use crate::bpf::fs::{BpfHandle, get_bpf_object};
10use crate::bpf::program::ProgramHandle;
11use crate::mm::PAGE_SIZE;
12use crate::task::CurrentTask;
13use crate::vfs::FdNumber;
14use crate::vfs::socket::{
15    SockOptValue, SocketDomain, SocketProtocol, SocketType, ZxioBackedSocket,
16};
17use ebpf::{EbpfProgram, EbpfProgramContext, ProgramArgument, Type};
18use ebpf_api::{
19    AttachType, BPF_SOCK_ADDR_TYPE, BPF_SOCK_TYPE, CgroupSockAddrProgramContext,
20    CgroupSockOptProgramContext, CgroupSockProgramContext, PinnedMap, ProgramType,
21    SocketCookieContext,
22};
23use fidl_fuchsia_net_filter as fnet_filter;
24use fuchsia_component::client::connect_to_protocol_sync;
25use starnix_logging::{log_error, log_warn, track_stub};
26use starnix_sync::{EbpfStateLock, FileOpsCore, Locked, OrderedRwLock, Unlocked};
27use starnix_syscalls::{SUCCESS, SyscallResult};
28use starnix_uapi::errors::{Errno, ErrnoCode};
29use starnix_uapi::{
30    CGROUP2_SUPER_MAGIC, bpf_attr__bindgen_ty_6, bpf_sock, bpf_sock_addr, errno, error,
31};
32use std::ops::{Deref, DerefMut};
33use std::sync::{Arc, OnceLock};
34use zerocopy::FromBytes;
35
36pub type BpfAttachAttr = bpf_attr__bindgen_ty_6;
37
38fn check_root_cgroup_fd(
39    locked: &mut Locked<Unlocked>,
40    current_task: &CurrentTask,
41    cgroup_fd: FdNumber,
42) -> Result<(), Errno> {
43    let file = current_task.files.get(cgroup_fd)?;
44
45    // Check that `cgroup_fd` is from the CGROUP2 file system.
46    let is_cgroup =
47        file.node().fs().statfs(locked, current_task)?.f_type == CGROUP2_SUPER_MAGIC as i64;
48    if !is_cgroup {
49        log_warn!("bpf_prog_attach(BPF_PROG_ATTACH) is called with an invalid cgroup2 FD.");
50        return error!(EINVAL);
51    }
52
53    // Currently cgroup attachments are supported only for the root cgroup.
54    // TODO(https://fxbug.dev//388077431) Allow attachments to any cgroup once cgroup
55    // hierarchy is moved to starnix_core.
56    let is_root = file
57        .node()
58        .fs()
59        .maybe_root()
60        .map(|root| Arc::ptr_eq(&root.node, file.node()))
61        .unwrap_or(false);
62    if !is_root {
63        log_warn!("bpf_prog_attach(BPF_PROG_ATTACH) is supported only for root cgroup.");
64        return error!(EINVAL);
65    }
66
67    Ok(())
68}
69
70pub fn bpf_prog_attach(
71    locked: &mut Locked<Unlocked>,
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    let attach_type = AttachType::from(attr.attach_type);
84
85    let program_type = program.info.program_type;
86    if attach_type.get_program_type() != program_type {
87        log_warn!(
88            "bpf_prog_attach(BPF_PROG_ATTACH): program not compatible with attach_type \
89                   attach_type: {attach_type:?}, program_type: {program_type:?}"
90        );
91        return error!(EINVAL);
92    }
93
94    if !attach_type.is_compatible_with_expected_attach_type(program.info.expected_attach_type) {
95        log_warn!(
96            "bpf_prog_attach(BPF_PROG_ATTACH): expected_attach_type didn't match attach_type \
97                   expected_attach_type: {:?}, attach_type: {:?}",
98            program.info.expected_attach_type,
99            attach_type
100        );
101        return error!(EINVAL);
102    }
103
104    // SAFETY: reading i32 field from a union is always safe.
105    let target_fd = unsafe { attr.__bindgen_anon_1.target_fd };
106    let target_fd = FdNumber::from_raw(target_fd as i32);
107
108    current_task.kernel().ebpf_state.attachments.attach_prog(
109        locked,
110        current_task,
111        attach_type,
112        target_fd,
113        program,
114    )
115}
116
117pub fn bpf_prog_detach(
118    locked: &mut Locked<Unlocked>,
119    current_task: &CurrentTask,
120    attr: BpfAttachAttr,
121) -> Result<SyscallResult, Errno> {
122    let attach_type = AttachType::from(attr.attach_type);
123
124    // SAFETY: reading i32 field from a union is always safe.
125    let target_fd = unsafe { attr.__bindgen_anon_1.target_fd };
126    let target_fd = FdNumber::from_raw(target_fd as i32);
127
128    current_task.kernel().ebpf_state.attachments.detach_prog(
129        locked,
130        current_task,
131        attach_type,
132        target_fd,
133    )
134}
135
136// Wrapper for `bpf_sock_addr` used to implement `ProgramArgument` trait.
137#[repr(C)]
138pub struct BpfSockAddr<'a> {
139    sock_addr: bpf_sock_addr,
140
141    socket: &'a ZxioBackedSocket,
142}
143
144impl<'a> Deref for BpfSockAddr<'a> {
145    type Target = bpf_sock_addr;
146    fn deref(&self) -> &Self::Target {
147        &self.sock_addr
148    }
149}
150
151impl<'a> DerefMut for BpfSockAddr<'a> {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        &mut self.sock_addr
154    }
155}
156
157impl<'a> ProgramArgument for &'_ mut BpfSockAddr<'a> {
158    fn get_type() -> &'static Type {
159        &*BPF_SOCK_ADDR_TYPE
160    }
161}
162
163impl<'a, 'b> SocketCookieContext<&'a mut BpfSockAddr<'a>> for EbpfRunContextImpl<'b> {
164    fn get_socket_cookie(&self, bpf_sock_addr: &'a mut BpfSockAddr<'a>) -> u64 {
165        let v = bpf_sock_addr.socket.get_socket_cookie();
166        v.unwrap_or_else(|errno| {
167            log_error!("Failed to get socket cookie: {:?}", errno);
168            0
169        })
170    }
171}
172
173// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCKADDR.
174struct SockAddrProgram(EbpfProgram<SockAddrProgram>);
175
176impl EbpfProgramContext for SockAddrProgram {
177    type RunContext<'a> = EbpfRunContextImpl<'a>;
178    type Packet<'a> = ();
179    type Arg1<'a> = &'a mut BpfSockAddr<'a>;
180    type Arg2<'a> = ();
181    type Arg3<'a> = ();
182    type Arg4<'a> = ();
183    type Arg5<'a> = ();
184
185    type Map = PinnedMap;
186}
187
188ebpf_api::ebpf_program_context_type!(SockAddrProgram, CgroupSockAddrProgramContext);
189
190#[derive(Debug, PartialEq, Eq)]
191pub enum SockAddrProgramResult {
192    Allow,
193    Block,
194}
195
196impl SockAddrProgram {
197    fn run<'a>(
198        &self,
199        locked: &'a mut Locked<EbpfStateLock>,
200        current_task: &'a CurrentTask,
201        addr: &'a mut BpfSockAddr<'a>,
202        can_block: bool,
203    ) -> SockAddrProgramResult {
204        let mut run_context = EbpfRunContextImpl::new(locked, current_task);
205        match self.0.run_with_1_argument(&mut run_context, addr) {
206            // UDP_RECVMSG programs are not allowed to block the packet.
207            0 if can_block => SockAddrProgramResult::Block,
208            1 => SockAddrProgramResult::Allow,
209            result => {
210                // TODO(https://fxbug.dev/413490751): Change this to panic once
211                // result validation is implemented in the eBPF verifier.
212                log_error!("eBPF program returned invalid result: {}", result);
213                SockAddrProgramResult::Allow
214            }
215        }
216    }
217}
218
219type AttachedSockAddrProgramCell = OrderedRwLock<Option<SockAddrProgram>, EbpfStateLock>;
220
221// Wrapper for `bpf_sock` used to implement `ProgramArgument` trait.
222#[repr(C)]
223pub struct BpfSock<'a> {
224    // Must be first field.
225    value: bpf_sock,
226
227    socket: &'a ZxioBackedSocket,
228}
229
230impl<'a> Deref for BpfSock<'a> {
231    type Target = bpf_sock;
232    fn deref(&self) -> &Self::Target {
233        &self.value
234    }
235}
236
237impl<'a> DerefMut for BpfSock<'a> {
238    fn deref_mut(&mut self) -> &mut Self::Target {
239        &mut self.value
240    }
241}
242
243impl<'a> ProgramArgument for &'_ BpfSock<'a> {
244    fn get_type() -> &'static Type {
245        &*BPF_SOCK_TYPE
246    }
247}
248
249impl<'a, 'b> SocketCookieContext<&'a BpfSock<'a>> for EbpfRunContextImpl<'b> {
250    fn get_socket_cookie(&self, bpf_sock: &'a BpfSock<'a>) -> u64 {
251        let v = bpf_sock.socket.get_socket_cookie();
252        v.unwrap_or_else(|errno| {
253            log_error!("Failed to get socket cookie: {:?}", errno);
254            0
255        })
256    }
257}
258
259// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCK.
260struct SockProgram(EbpfProgram<SockProgram>);
261
262impl EbpfProgramContext for SockProgram {
263    type RunContext<'a> = EbpfRunContextImpl<'a>;
264    type Packet<'a> = ();
265    type Arg1<'a> = &'a BpfSock<'a>;
266    type Arg2<'a> = ();
267    type Arg3<'a> = ();
268    type Arg4<'a> = ();
269    type Arg5<'a> = ();
270
271    type Map = PinnedMap;
272}
273
274ebpf_api::ebpf_program_context_type!(SockProgram, CgroupSockProgramContext);
275
276#[derive(Debug, PartialEq, Eq)]
277pub enum SockProgramResult {
278    Allow,
279    Block,
280}
281
282impl SockProgram {
283    fn run<'a>(
284        &self,
285        locked: &mut Locked<EbpfStateLock>,
286        current_task: &'a CurrentTask,
287        sock: &'a BpfSock<'a>,
288    ) -> SockProgramResult {
289        let mut run_context = EbpfRunContextImpl::new(locked, current_task);
290        if self.0.run_with_1_argument(&mut run_context, sock) == 0 {
291            SockProgramResult::Block
292        } else {
293            SockProgramResult::Allow
294        }
295    }
296}
297
298type AttachedSockProgramCell = OrderedRwLock<Option<SockProgram>, EbpfStateLock>;
299
300mod internal {
301    use ebpf::{ProgramArgument, Type};
302    use ebpf_api::BPF_SOCKOPT_TYPE;
303    use starnix_uapi::{bpf_sockopt, uaddr};
304    use std::ops::Deref;
305
306    // Wrapper for `bpf_sockopt` that implements `ProgramArgument` trait and
307    // keeps a buffer for the `optval`.
308    #[repr(C)]
309    pub struct BpfSockOpt {
310        sockopt: bpf_sockopt,
311
312        // Buffer used to store the option value. A pointer to the buffer
313        // contents is stored in `sockopt`. `Vec::as_mut_ptr()` guarantees that
314        // the pointer remains valid only as long as the `Vec` is not modified,
315        // so this field should not be updated directly. `take_value()` can be
316        // used to extract the value when `BpfSockOpt` is no longer needed.
317        value_buf: Vec<u8>,
318    }
319
320    impl BpfSockOpt {
321        pub fn new(level: u32, optname: u32, value_buf: Vec<u8>, optlen: u32, retval: i32) -> Self {
322            let mut sockopt = Self {
323                sockopt: bpf_sockopt {
324                    level: level as i32,
325                    optname: optname as i32,
326                    optlen: optlen as i32,
327                    retval: retval as i32,
328                    ..Default::default()
329                },
330                value_buf,
331            };
332
333            // SAFETY: Setting buffer bounds in unions is safe.
334            unsafe {
335                sockopt.sockopt.__bindgen_anon_2.optval =
336                    uaddr { addr: sockopt.value_buf.as_mut_ptr() as u64 };
337                sockopt.sockopt.__bindgen_anon_3.optval_end = uaddr {
338                    addr: sockopt.value_buf.as_mut_ptr().add(sockopt.value_buf.len()) as u64,
339                };
340            }
341
342            sockopt
343        }
344
345        // Returns the value. Consumes `self` since it's not safe to use again
346        // after the value buffer is moved.
347        pub fn take_value(self) -> Vec<u8> {
348            self.value_buf
349        }
350    }
351
352    impl Deref for BpfSockOpt {
353        type Target = bpf_sockopt;
354        fn deref(&self) -> &Self::Target {
355            &self.sockopt
356        }
357    }
358
359    impl ProgramArgument for &'_ mut BpfSockOpt {
360        fn get_type() -> &'static Type {
361            &*BPF_SOCKOPT_TYPE
362        }
363    }
364}
365
366use internal::BpfSockOpt;
367
368// Context for eBPF programs of type BPF_PROG_TYPE_CGROUP_SOCKOPT.
369struct SockOptProgram(EbpfProgram<SockOptProgram>);
370
371impl EbpfProgramContext for SockOptProgram {
372    type RunContext<'a> = EbpfRunContextImpl<'a>;
373    type Packet<'a> = ();
374    type Arg1<'a> = &'a mut BpfSockOpt;
375    type Arg2<'a> = ();
376    type Arg3<'a> = ();
377    type Arg4<'a> = ();
378    type Arg5<'a> = ();
379
380    type Map = PinnedMap;
381}
382
383ebpf_api::ebpf_program_context_type!(SockOptProgram, CgroupSockOptProgramContext);
384
385#[derive(Debug)]
386pub enum SetSockOptProgramResult {
387    /// Fail the syscall.
388    Fail(Errno),
389
390    /// Proceed with the specified option value.
391    Allow(SockOptValue),
392
393    /// Return to userspace without invoking the underlying implementation of
394    /// setsockopt.
395    Bypass,
396}
397
398impl SockOptProgram {
399    fn run<'a>(
400        &self,
401        locked: &mut Locked<EbpfStateLock>,
402        current_task: &'a CurrentTask,
403        sockopt: &'a mut BpfSockOpt,
404    ) -> u64 {
405        let mut run_context = EbpfRunContextImpl::new(locked, current_task);
406        self.0.run_with_1_argument(&mut run_context, sockopt)
407    }
408}
409
410type AttachedSockOptProgramCell = OrderedRwLock<Option<SockOptProgram>, EbpfStateLock>;
411
412#[derive(Default)]
413pub struct CgroupEbpfProgramSet {
414    inet4_bind: AttachedSockAddrProgramCell,
415    inet6_bind: AttachedSockAddrProgramCell,
416    inet4_connect: AttachedSockAddrProgramCell,
417    inet6_connect: AttachedSockAddrProgramCell,
418    udp4_sendmsg: AttachedSockAddrProgramCell,
419    udp6_sendmsg: AttachedSockAddrProgramCell,
420    udp4_recvmsg: AttachedSockAddrProgramCell,
421    udp6_recvmsg: AttachedSockAddrProgramCell,
422    sock_create: AttachedSockProgramCell,
423    sock_release: AttachedSockProgramCell,
424    set_sockopt: AttachedSockOptProgramCell,
425    get_sockopt: AttachedSockOptProgramCell,
426}
427
428#[derive(Eq, PartialEq, Debug, Copy, Clone)]
429pub enum SockAddrOp {
430    Bind,
431    Connect,
432    UdpSendMsg,
433    UdpRecvMsg,
434}
435
436#[derive(Eq, PartialEq, Debug, Copy, Clone)]
437pub enum SockOp {
438    Create,
439    Release,
440}
441
442impl CgroupEbpfProgramSet {
443    fn get_sock_addr_program(
444        &self,
445        attach_type: AttachType,
446    ) -> Result<&AttachedSockAddrProgramCell, Errno> {
447        assert!(attach_type.is_cgroup());
448
449        match attach_type {
450            AttachType::CgroupInet4Bind => Ok(&self.inet4_bind),
451            AttachType::CgroupInet6Bind => Ok(&self.inet6_bind),
452            AttachType::CgroupInet4Connect => Ok(&self.inet4_connect),
453            AttachType::CgroupInet6Connect => Ok(&self.inet6_connect),
454            AttachType::CgroupUdp4Sendmsg => Ok(&self.udp4_sendmsg),
455            AttachType::CgroupUdp6Sendmsg => Ok(&self.udp6_sendmsg),
456            AttachType::CgroupUdp4Recvmsg => Ok(&self.udp4_recvmsg),
457            AttachType::CgroupUdp6Recvmsg => Ok(&self.udp6_recvmsg),
458            _ => error!(ENOTSUP),
459        }
460    }
461
462    fn get_sock_program(&self, attach_type: AttachType) -> Result<&AttachedSockProgramCell, Errno> {
463        assert!(attach_type.is_cgroup());
464
465        match attach_type {
466            AttachType::CgroupInetSockCreate => Ok(&self.sock_create),
467            AttachType::CgroupInetSockRelease => Ok(&self.sock_release),
468            _ => error!(ENOTSUP),
469        }
470    }
471
472    fn get_sock_opt_program(
473        &self,
474        attach_type: AttachType,
475    ) -> Result<&AttachedSockOptProgramCell, Errno> {
476        assert!(attach_type.is_cgroup());
477
478        match attach_type {
479            AttachType::CgroupSetsockopt => Ok(&self.set_sockopt),
480            AttachType::CgroupGetsockopt => Ok(&self.get_sockopt),
481            _ => error!(ENOTSUP),
482        }
483    }
484
485    // Executes eBPF program for the operation `op`. `socket_address` contains
486    // socket address as a `sockaddr` struct.
487    pub fn run_sock_addr_prog(
488        &self,
489        locked: &mut Locked<FileOpsCore>,
490        current_task: &CurrentTask,
491        op: SockAddrOp,
492        domain: SocketDomain,
493        socket_type: SocketType,
494        protocol: SocketProtocol,
495        socket_address: &[u8],
496        socket: &ZxioBackedSocket,
497    ) -> Result<SockAddrProgramResult, Errno> {
498        let prog_cell = match (domain, op) {
499            (SocketDomain::Inet, SockAddrOp::Bind) => &self.inet4_bind,
500            (SocketDomain::Inet6, SockAddrOp::Bind) => &self.inet6_bind,
501            (SocketDomain::Inet, SockAddrOp::Connect) => &self.inet4_connect,
502            (SocketDomain::Inet6, SockAddrOp::Connect) => &self.inet6_connect,
503            (SocketDomain::Inet, SockAddrOp::UdpSendMsg) => &self.udp4_sendmsg,
504            (SocketDomain::Inet6, SockAddrOp::UdpSendMsg) => &self.udp6_sendmsg,
505            (SocketDomain::Inet, SockAddrOp::UdpRecvMsg) => &self.udp4_recvmsg,
506            (SocketDomain::Inet6, SockAddrOp::UdpRecvMsg) => &self.udp6_recvmsg,
507            _ => return Ok(SockAddrProgramResult::Allow),
508        };
509
510        let (prog_guard, locked) = prog_cell.read_and(locked);
511        let Some(prog) = prog_guard.as_ref() else {
512            return Ok(SockAddrProgramResult::Allow);
513        };
514
515        let mut bpf_sockaddr = BpfSockAddr { sock_addr: Default::default(), socket };
516        bpf_sockaddr.family = domain.as_raw().into();
517        bpf_sockaddr.type_ = socket_type.as_raw();
518        bpf_sockaddr.protocol = protocol.as_raw();
519
520        let (sa_family, _) = u16::read_from_prefix(socket_address).map_err(|_| errno!(EINVAL))?;
521
522        if domain.as_raw() != sa_family {
523            return error!(EAFNOSUPPORT);
524        }
525        bpf_sockaddr.user_family = sa_family.into();
526
527        match sa_family.into() {
528            linux_uapi::AF_INET => {
529                let (sockaddr, _) = linux_uapi::sockaddr_in::ref_from_prefix(socket_address)
530                    .map_err(|_| errno!(EINVAL))?;
531                bpf_sockaddr.user_port = sockaddr.sin_port.into();
532                bpf_sockaddr.user_ip4 = sockaddr.sin_addr.s_addr;
533            }
534            linux_uapi::AF_INET6 => {
535                let sockaddr = linux_uapi::sockaddr_in6::ref_from_prefix(socket_address)
536                    .map_err(|_| errno!(EINVAL))?
537                    .0;
538                bpf_sockaddr.user_port = sockaddr.sin6_port.into();
539                // SAFETY: reading an array of u32 from a union is safe.
540                bpf_sockaddr.user_ip6 = unsafe { sockaddr.sin6_addr.in6_u.u6_addr32 };
541            }
542            _ => return error!(EAFNOSUPPORT),
543        }
544
545        // UDP recvmsg programs are not allowed to filter packets.
546        let can_block = op != SockAddrOp::UdpRecvMsg;
547        Ok(prog.run(locked, current_task, &mut bpf_sockaddr, can_block))
548    }
549
550    pub fn run_sock_prog(
551        &self,
552        locked: &mut Locked<FileOpsCore>,
553        current_task: &CurrentTask,
554        op: SockOp,
555        domain: SocketDomain,
556        socket_type: SocketType,
557        protocol: SocketProtocol,
558        socket: &ZxioBackedSocket,
559    ) -> SockProgramResult {
560        let prog_cell = match op {
561            SockOp::Create => &self.sock_create,
562            SockOp::Release => &self.sock_release,
563        };
564        let (prog_guard, locked) = prog_cell.read_and(locked);
565        let Some(prog) = prog_guard.as_ref() else {
566            return SockProgramResult::Allow;
567        };
568
569        let bpf_sock = BpfSock {
570            value: bpf_sock {
571                family: domain.as_raw().into(),
572                type_: socket_type.as_raw(),
573                protocol: protocol.as_raw(),
574                ..Default::default()
575            },
576            socket,
577        };
578
579        prog.run(locked, current_task, &bpf_sock)
580    }
581
582    pub fn run_getsockopt_prog(
583        &self,
584        locked: &mut Locked<FileOpsCore>,
585        current_task: &CurrentTask,
586        level: u32,
587        optname: u32,
588        optval: Vec<u8>,
589        optlen: usize,
590        error: Option<Errno>,
591    ) -> Result<(Vec<u8>, usize), Errno> {
592        let (prog_guard, locked) = self.get_sockopt.read_and(locked);
593        let Some(prog) = prog_guard.as_ref() else {
594            return error.map(|e| Err(e)).unwrap_or_else(|| Ok((optval, optlen)));
595        };
596
597        let retval = error.as_ref().map(|e| -(e.code.error_code() as i32)).unwrap_or(0);
598        let mut bpf_sockopt =
599            BpfSockOpt::new(level, optname, optval.clone(), optlen as u32, retval);
600
601        // Run the program.
602        let result = prog.run(locked, current_task, &mut bpf_sockopt);
603
604        if bpf_sockopt.retval < 0 {
605            return Err(Errno::new(ErrnoCode::from_return_value(bpf_sockopt.retval as u64)));
606        }
607
608        match (result, bpf_sockopt.optlen) {
609            // Reject the call if the program returned 0.
610            (0, _) => error!(EPERM),
611
612            // Fail if the program has set an invalid `optlen` (except for the
613            // case handled above).
614            (1, optlen) if optlen < 0 || (optlen as usize) > optval.len() => {
615                error!(EFAULT)
616            }
617
618            // If `optlen` is set to 0 then proceed with the original value.
619            (1, 0) => Ok((optval, optlen)),
620
621            // Return value from `bpf_sockbuf` - it may be different from the
622            // original value.
623            (1, new_optlen) => Ok((bpf_sockopt.take_value(), new_optlen as usize)),
624
625            (result, _) => {
626                // TODO(https://fxbug.dev/413490751): Change this to panic once
627                // result validation is implemented in the verifier.
628                log_error!("eBPF getsockopt program returned invalid result: {}", result);
629                Ok((optval, optlen))
630            }
631        }
632    }
633
634    pub fn run_setsockopt_prog(
635        &self,
636        locked: &mut Locked<FileOpsCore>,
637        current_task: &CurrentTask,
638        level: u32,
639        optname: u32,
640        value: SockOptValue,
641    ) -> SetSockOptProgramResult {
642        let (prog_guard, locked) = self.set_sockopt.read_and(locked);
643        let Some(prog) = prog_guard.as_ref() else {
644            return SetSockOptProgramResult::Allow(value);
645        };
646
647        let page_size = *PAGE_SIZE as usize;
648
649        // Read only the first page from the user-specified buffer in case it's
650        // larger than that.
651        let buffer = match value.read_bytes(current_task, page_size) {
652            Ok(buffer) => buffer,
653            Err(err) => return SetSockOptProgramResult::Fail(err),
654        };
655
656        let buffer_len = buffer.len();
657        let optlen = value.len();
658        let mut bpf_sockopt = BpfSockOpt::new(level, optname, buffer, optlen as u32, 0);
659        let result = prog.run(locked.cast_locked(), current_task, &mut bpf_sockopt);
660
661        match (result, bpf_sockopt.optlen) {
662            // Reject the call if the program returned 0.
663            (0, _) => SetSockOptProgramResult::Fail(errno!(EPERM)),
664
665            // `setsockopt` programs can bypass the platform implementation by
666            // setting `optlen` to -1.
667            (1, -1) => SetSockOptProgramResult::Bypass,
668
669            // If the original value is larger than a page and the program
670            // didn't change `optlen` then return the original value. This
671            // allows to avoid `EFAULT` below with a no-op program.
672            (1, new_optlen) if optlen > page_size && (new_optlen as usize) == optlen => {
673                SetSockOptProgramResult::Allow(value)
674            }
675
676            // Fail if the program has set an invalid `optlen` (except for the
677            // case handled above).
678            (1, optlen) if optlen < 0 || (optlen as usize) > buffer_len => {
679                SetSockOptProgramResult::Fail(errno!(EFAULT))
680            }
681
682            // If `optlen` is set to 0 then proceed with the original value.
683            (1, 0) => SetSockOptProgramResult::Allow(value),
684
685            // Return value from `bpf_sockbuf` - it may be different from the
686            // original value.
687            (1, optlen) => {
688                let mut value = bpf_sockopt.take_value();
689                value.resize(optlen as usize, 0);
690                SetSockOptProgramResult::Allow(value.into())
691            }
692
693            (result, _) => {
694                // TODO(https://fxbug.dev/413490751): Change this to panic once
695                // result validation is implemented in the verifier.
696                log_error!("eBPF setsockopt program returned invalid result: {}", result);
697                SetSockOptProgramResult::Allow(value)
698            }
699        }
700    }
701}
702
703fn attach_type_to_netstack_hook(attach_type: AttachType) -> Option<fnet_filter::SocketHook> {
704    let hook = match attach_type {
705        AttachType::CgroupInetEgress => fnet_filter::SocketHook::Egress,
706        AttachType::CgroupInetIngress => fnet_filter::SocketHook::Ingress,
707        _ => return None,
708    };
709    Some(hook)
710}
711
712// Defined a location where eBPF programs can be attached.
713#[derive(Copy, Clone, Debug, PartialEq, Eq)]
714enum AttachLocation {
715    // Attached in Starnix kernel.
716    Kernel,
717
718    // Attached in Netstack.
719    Netstack,
720}
721
722impl TryFrom<AttachType> for AttachLocation {
723    type Error = Errno;
724
725    fn try_from(attach_type: AttachType) -> Result<Self, Self::Error> {
726        match attach_type {
727            AttachType::CgroupInet4Bind
728            | AttachType::CgroupInet6Bind
729            | AttachType::CgroupInet4Connect
730            | AttachType::CgroupInet6Connect
731            | AttachType::CgroupUdp4Sendmsg
732            | AttachType::CgroupUdp6Sendmsg
733            | AttachType::CgroupUdp4Recvmsg
734            | AttachType::CgroupUdp6Recvmsg
735            | AttachType::CgroupInetSockCreate
736            | AttachType::CgroupInetSockRelease
737            | AttachType::CgroupGetsockopt
738            | AttachType::CgroupSetsockopt => Ok(AttachLocation::Kernel),
739
740            AttachType::CgroupInetEgress | AttachType::CgroupInetIngress => {
741                Ok(AttachLocation::Netstack)
742            }
743
744            AttachType::CgroupDevice
745            | AttachType::CgroupInet4Getpeername
746            | AttachType::CgroupInet4Getsockname
747            | AttachType::CgroupInet4PostBind
748            | AttachType::CgroupInet6Getpeername
749            | AttachType::CgroupInet6Getsockname
750            | AttachType::CgroupInet6PostBind
751            | AttachType::CgroupSysctl
752            | AttachType::CgroupUnixConnect
753            | AttachType::CgroupUnixGetpeername
754            | AttachType::CgroupUnixGetsockname
755            | AttachType::CgroupUnixRecvmsg
756            | AttachType::CgroupUnixSendmsg
757            | AttachType::CgroupSockOps
758            | AttachType::SkSkbStreamParser
759            | AttachType::SkSkbStreamVerdict
760            | AttachType::SkMsgVerdict
761            | AttachType::LircMode2
762            | AttachType::FlowDissector
763            | AttachType::TraceRawTp
764            | AttachType::TraceFentry
765            | AttachType::TraceFexit
766            | AttachType::ModifyReturn
767            | AttachType::LsmMac
768            | AttachType::TraceIter
769            | AttachType::XdpDevmap
770            | AttachType::XdpCpumap
771            | AttachType::SkLookup
772            | AttachType::Xdp
773            | AttachType::SkSkbVerdict
774            | AttachType::SkReuseportSelect
775            | AttachType::SkReuseportSelectOrMigrate
776            | AttachType::PerfEvent
777            | AttachType::TraceKprobeMulti
778            | AttachType::LsmCgroup
779            | AttachType::StructOps
780            | AttachType::Netfilter
781            | AttachType::TcxIngress
782            | AttachType::TcxEgress
783            | AttachType::TraceUprobeMulti
784            | AttachType::NetkitPrimary
785            | AttachType::NetkitPeer
786            | AttachType::TraceKprobeSession => {
787                track_stub!(TODO("https://fxbug.dev/322873416"), "BPF_PROG_ATTACH", attach_type);
788                error!(ENOTSUP)
789            }
790
791            AttachType::Unspecified | AttachType::Invalid(_) => {
792                error!(EINVAL)
793            }
794        }
795    }
796}
797
798#[derive(Default)]
799pub struct EbpfAttachments {
800    root_cgroup: CgroupEbpfProgramSet,
801    socket_control: OnceLock<fnet_filter::SocketControlSynchronousProxy>,
802}
803
804impl EbpfAttachments {
805    pub fn root_cgroup(&self) -> &CgroupEbpfProgramSet {
806        &self.root_cgroup
807    }
808
809    fn socket_control(&self) -> &fnet_filter::SocketControlSynchronousProxy {
810        self.socket_control.get_or_init(|| {
811            connect_to_protocol_sync::<fnet_filter::SocketControlMarker>()
812                .expect("Failed to connect to fuchsia.net.filter.SocketControl.")
813        })
814    }
815
816    fn attach_prog(
817        &self,
818        locked: &mut Locked<Unlocked>,
819        current_task: &CurrentTask,
820        attach_type: AttachType,
821        target_fd: FdNumber,
822        program: ProgramHandle,
823    ) -> Result<SyscallResult, Errno> {
824        let location: AttachLocation = attach_type.try_into()?;
825        let program_type = attach_type.get_program_type();
826        match (location, program_type) {
827            (AttachLocation::Kernel, ProgramType::CgroupSockAddr) => {
828                check_root_cgroup_fd(locked, current_task, target_fd)?;
829
830                let linked_program = SockAddrProgram(program.link(attach_type.get_program_type())?);
831                *self.root_cgroup.get_sock_addr_program(attach_type)?.write(locked) =
832                    Some(linked_program);
833
834                Ok(SUCCESS)
835            }
836
837            (AttachLocation::Kernel, ProgramType::CgroupSock) => {
838                check_root_cgroup_fd(locked, current_task, target_fd)?;
839
840                let linked_program = SockProgram(program.link(attach_type.get_program_type())?);
841                *self.root_cgroup.get_sock_program(attach_type)?.write(locked) =
842                    Some(linked_program);
843
844                Ok(SUCCESS)
845            }
846
847            (AttachLocation::Kernel, ProgramType::CgroupSockopt) => {
848                check_root_cgroup_fd(locked, current_task, target_fd)?;
849
850                let linked_program = SockOptProgram(program.link(attach_type.get_program_type())?);
851                *self.root_cgroup.get_sock_opt_program(attach_type)?.write(locked) =
852                    Some(linked_program);
853
854                Ok(SUCCESS)
855            }
856
857            (AttachLocation::Kernel, _) => {
858                unreachable!();
859            }
860
861            (AttachLocation::Netstack, _) => {
862                check_root_cgroup_fd(locked, current_task, target_fd)?;
863                self.attach_prog_in_netstack(attach_type, program)
864            }
865        }
866    }
867
868    fn detach_prog(
869        &self,
870        locked: &mut Locked<Unlocked>,
871        current_task: &CurrentTask,
872        attach_type: AttachType,
873        target_fd: FdNumber,
874    ) -> Result<SyscallResult, Errno> {
875        let location = attach_type.try_into()?;
876        let program_type = attach_type.get_program_type();
877        match (location, program_type) {
878            (AttachLocation::Kernel, ProgramType::CgroupSockAddr) => {
879                check_root_cgroup_fd(locked, current_task, target_fd)?;
880
881                let mut prog_guard =
882                    self.root_cgroup.get_sock_addr_program(attach_type)?.write(locked);
883                if prog_guard.is_none() {
884                    return error!(ENOENT);
885                }
886
887                *prog_guard = None;
888
889                Ok(SUCCESS)
890            }
891
892            (AttachLocation::Kernel, ProgramType::CgroupSock) => {
893                check_root_cgroup_fd(locked, current_task, target_fd)?;
894
895                let mut prog_guard = self.root_cgroup.get_sock_program(attach_type)?.write(locked);
896                if prog_guard.is_none() {
897                    return error!(ENOENT);
898                }
899
900                *prog_guard = None;
901
902                Ok(SUCCESS)
903            }
904
905            (AttachLocation::Kernel, ProgramType::CgroupSockopt) => {
906                check_root_cgroup_fd(locked, current_task, target_fd)?;
907
908                let mut prog_guard =
909                    self.root_cgroup.get_sock_opt_program(attach_type)?.write(locked);
910                if prog_guard.is_none() {
911                    return error!(ENOENT);
912                }
913
914                *prog_guard = None;
915
916                Ok(SUCCESS)
917            }
918
919            (AttachLocation::Kernel, _) => {
920                unreachable!();
921            }
922
923            (AttachLocation::Netstack, _) => {
924                check_root_cgroup_fd(locked, current_task, target_fd)?;
925                self.detach_prog_in_netstack(attach_type)
926            }
927        }
928    }
929
930    fn attach_prog_in_netstack(
931        &self,
932        attach_type: AttachType,
933        program: ProgramHandle,
934    ) -> Result<SyscallResult, Errno> {
935        let hook = attach_type_to_netstack_hook(attach_type).ok_or_else(|| errno!(ENOTSUP))?;
936        let opts = fnet_filter::AttachEbpfProgramOptions {
937            hook: Some(hook),
938            program: Some((&**program).try_into()?),
939            ..Default::default()
940        };
941        self.socket_control()
942            .attach_ebpf_program(opts, zx::MonotonicInstant::INFINITE)
943            .map_err(|e| {
944                log_error!(
945                    "failed to send fuchsia.net.filter/SocketControl.AttachEbpfProgram: {}",
946                    e
947                );
948                errno!(EIO)
949            })?
950            .map_err(|e| {
951                use fnet_filter::SocketControlAttachEbpfProgramError as Error;
952                match e {
953                    Error::NotSupported => errno!(ENOTSUP),
954                    Error::LinkFailed => errno!(EINVAL),
955                    Error::MapFailed => errno!(EIO),
956                    Error::DuplicateAttachment => errno!(EEXIST),
957                }
958            })?;
959
960        Ok(SUCCESS)
961    }
962
963    fn detach_prog_in_netstack(&self, attach_type: AttachType) -> Result<SyscallResult, Errno> {
964        let hook = attach_type_to_netstack_hook(attach_type).ok_or_else(|| errno!(ENOTSUP))?;
965        self.socket_control()
966            .detach_ebpf_program(hook, zx::MonotonicInstant::INFINITE)
967            .map_err(|e| {
968                log_error!(
969                    "failed to send fuchsia.net.filter/SocketControl.DetachEbpfProgram: {}",
970                    e
971                );
972                errno!(EIO)
973            })?
974            .map_err(|e| {
975                use fnet_filter::SocketControlDetachEbpfProgramError as Error;
976                match e {
977                    Error::NotFound => errno!(ENOENT),
978                }
979            })?;
980        Ok(SUCCESS)
981    }
982}