1use crate::bpf::BpfMapHandle;
6use crate::bpf::fs::get_bpf_object;
7use crate::security;
8use crate::task::{CurrentTask, Kernel, register_delayed_release};
9use crate::vfs::{FdNumber, OutputBuffer};
10use ebpf::{
11 BPF_LDDW, BPF_PSEUDO_BTF_ID, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_IDX,
12 BPF_PSEUDO_MAP_IDX_VALUE, BPF_PSEUDO_MAP_VALUE, EbpfInstruction, EbpfProgram,
13 EbpfProgramContext, StaticHelperSet, VerifiedEbpfProgram, VerifierLogger, link_program,
14 verify_program,
15};
16use ebpf_api::{AttachType, EbpfApiError, MapsContext, PinnedMap, ProgramType, StructId};
17use fidl_fuchsia_ebpf as febpf;
18use starnix_lifecycle::{AtomicCounter, ObjectReleaser, ReleaserAction};
19use starnix_logging::{log_warn, track_stub};
20use starnix_types::ownership::{Releasable, ReleaseGuard};
21use starnix_uapi::auth::{CAP_BPF, CAP_NET_ADMIN, CAP_PERFMON, CAP_SYS_ADMIN};
22use starnix_uapi::errors::Errno;
23use starnix_uapi::{bpf_attr__bindgen_ty_4, errno, error};
24use std::sync::{Arc, Weak};
25
26#[derive(Clone, Debug)]
27pub struct ProgramInfo {
28 pub program_type: ProgramType,
29 pub expected_attach_type: AttachType,
30}
31
32impl TryFrom<&bpf_attr__bindgen_ty_4> for ProgramInfo {
33 type Error = Errno;
34
35 fn try_from(info: &bpf_attr__bindgen_ty_4) -> Result<Self, Self::Error> {
36 Ok(Self {
37 program_type: info.prog_type.try_into().map_err(map_ebpf_api_error)?,
38 expected_attach_type: info.expected_attach_type.into(),
39 })
40 }
41}
42pub type ProgramId = u32;
43
44static NEXT_PROGRAM_ID: AtomicCounter<u32> = AtomicCounter::<u32>::new_const(1);
45fn new_program_id() -> ProgramId {
46 NEXT_PROGRAM_ID.next()
47}
48
49#[derive(Debug)]
50pub struct Program {
51 pub info: ProgramInfo,
53
54 program: VerifiedEbpfProgram,
56
57 maps: Vec<BpfMapHandle>,
60
61 id: ProgramId,
64
65 fidl_handle: febpf::ProgramHandle,
67
68 fidl_id: febpf::ProgramId,
70
71 #[allow(dead_code)]
74 service_handle: zx::EventPair,
75
76 kernel: Weak<Kernel>,
78
79 pub security_state: security::BpfProgState,
81}
82
83fn map_ebpf_api_error(e: EbpfApiError) -> Errno {
84 match e {
85 EbpfApiError::InvalidProgramType(_) | EbpfApiError::InvalidExpectedAttachType(_) => {
86 errno!(EINVAL, e)
87 }
88 EbpfApiError::UnsupportedProgramType(_) => errno!(ENOTSUP, e),
89 }
90}
91
92impl Program {
93 pub fn new(
94 current_task: &CurrentTask,
95 info: ProgramInfo,
96 logger: &mut dyn OutputBuffer,
97 mut code: Vec<EbpfInstruction>,
98 ) -> Result<ProgramHandle, Errno> {
99 Self::check_load_access(current_task, &info)?;
100 let maps = link_maps_fds(current_task, &mut code)?;
101 let maps_schema = maps.iter().map(|m| m.schema).collect();
102 let mut logger = BufferVeriferLogger::new(logger);
103 let calling_context = info
104 .program_type
105 .create_calling_context(info.expected_attach_type, maps_schema)
106 .map_err(map_ebpf_api_error)?;
107 let program = verify_program(code, calling_context, &mut logger)
108 .map_err(|err| errno!(EINVAL, err))?;
109
110 let (fidl_handle, service_handle) = zx::EventPair::create();
111 let fidl_id =
112 febpf::ProgramId { id: fidl_handle.koid().expect("Failed to get koid").raw_koid() };
113 let fidl_handle = febpf::ProgramHandle { handle: fidl_handle };
114
115 let program = ProgramHandle::new(
116 Self {
117 info,
118 program,
119 maps,
120 id: new_program_id(),
121 fidl_handle,
122 fidl_id,
123 service_handle,
124 kernel: Arc::downgrade(current_task.kernel()),
125 security_state: security::bpf_prog_alloc(current_task),
126 }
127 .into(),
128 );
129 current_task.kernel().ebpf_state.register_program(&program);
130
131 Ok(program)
132 }
133
134 pub fn id(&self) -> ProgramId {
135 self.id
136 }
137
138 pub fn link<C: EbpfProgramContext<Map = PinnedMap> + StaticHelperSet>(
139 &self,
140 program_type: ProgramType,
141 ) -> Result<EbpfProgram<C>, Errno>
142 where
143 for<'a> C::RunContext<'a>: MapsContext<'a>,
144 {
145 if program_type != self.info.program_type {
146 return error!(EINVAL);
147 }
148
149 let maps = self.maps.iter().map(|map| map.get_inner()).collect();
150 let program = link_program(&self.program, maps).map_err(|err| errno!(EINVAL, err))?;
151
152 Ok(program)
153 }
154
155 fn check_load_access(current_task: &CurrentTask, info: &ProgramInfo) -> Result<(), Errno> {
156 if matches!(info.program_type, ProgramType::CgroupSkb | ProgramType::SocketFilter)
157 && current_task.kernel().allow_unprivileged_bpf()
158 {
159 return Ok(());
160 }
161 if security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN) {
162 return Ok(());
163 }
164 security::check_task_capable(current_task, CAP_BPF)?;
165 match info.program_type {
166 ProgramType::Kprobe
168 | ProgramType::Tracepoint
169 | ProgramType::PerfEvent
170 | ProgramType::RawTracepoint
171 | ProgramType::RawTracepointWritable
172 | ProgramType::Tracing => security::check_task_capable(current_task, CAP_PERFMON),
173
174 ProgramType::SocketFilter
176 | ProgramType::SchedCls
177 | ProgramType::SchedAct
178 | ProgramType::Xdp
179 | ProgramType::SockOps
180 | ProgramType::SkSkb
181 | ProgramType::SkMsg
182 | ProgramType::SkLookup
183 | ProgramType::SkReuseport
184 | ProgramType::FlowDissector
185 | ProgramType::Netfilter => security::check_task_capable(current_task, CAP_NET_ADMIN),
186
187 ProgramType::CgroupDevice
189 | ProgramType::CgroupSkb
190 | ProgramType::CgroupSock
191 | ProgramType::CgroupSockAddr
192 | ProgramType::CgroupSockopt
193 | ProgramType::CgroupSysctl
194 | ProgramType::Ext
195 | ProgramType::LircMode2
196 | ProgramType::Lsm
197 | ProgramType::LwtIn
198 | ProgramType::LwtOut
199 | ProgramType::LwtSeg6Local
200 | ProgramType::LwtXmit
201 | ProgramType::StructOps
202 | ProgramType::Syscall
203 | ProgramType::Unspec
204 | ProgramType::Fuse => Ok(()),
205 }
206 }
207
208 pub fn fidl_id(&self) -> febpf::ProgramId {
209 self.fidl_id
210 }
211
212 pub fn fidl_handle(&self) -> febpf::ProgramHandle {
213 let handle = self
214 .fidl_handle
215 .handle
216 .duplicate_handle(zx::Rights::TRANSFER | zx::Rights::SIGNAL | zx::Rights::WAIT)
217 .expect("Failed to duplicate handle");
218 febpf::ProgramHandle { handle }
219 }
220}
221
222impl Releasable for Program {
223 type Context<'a> = &'a CurrentTask;
224
225 fn release<'a>(self, _current_task: &'a CurrentTask) {
226 if let Some(kernel) = self.kernel.upgrade() {
227 kernel.ebpf_state.unregister_program(self.id);
228 }
229
230 self.fidl_handle
233 .handle
234 .signal(
235 zx::Signals::NONE,
236 zx::Signals::from_bits_truncate(febpf::PROGRAM_DEFUNCT_SIGNAL),
237 )
238 .unwrap();
239 }
240}
241
242pub enum ProgramReleaserAction {}
243impl ReleaserAction<Program> for ProgramReleaserAction {
244 fn release(program: ReleaseGuard<Program>) {
245 register_delayed_release(program);
246 }
247}
248pub type ProgramReleaser = ObjectReleaser<Program, ProgramReleaserAction>;
249pub type ProgramHandle = Arc<ProgramReleaser>;
250pub type WeakProgramHandle = Weak<ProgramReleaser>;
251
252impl TryFrom<&Program> for febpf::VerifiedProgram {
253 type Error = Errno;
254
255 fn try_from(program: &Program) -> Result<febpf::VerifiedProgram, Errno> {
256 let mut maps = Vec::with_capacity(program.maps.len());
257 for map in program.maps.iter() {
258 maps.push(map.share().map_err(|_| errno!(EIO))?);
259 }
260
261 let code_u64: &[u64] = zerocopy::transmute_ref!(program.program.code());
262
263 let mut struct_access_instructions =
264 Vec::with_capacity(program.program.struct_access_instructions().len());
265 for v in program.program.struct_access_instructions() {
266 let struct_id = StructId::try_from(&v.memory_id).map_err(|()| errno!(EINVAL))?.into();
267 struct_access_instructions.push(febpf::StructAccess {
268 pc: v.pc.try_into().unwrap(),
269 struct_id,
270 field_offset: v.field_offset.try_into().unwrap(),
271 is_32_bit_ptr_load: v.is_32_bit_ptr_load,
272 })
273 }
274 Ok(febpf::VerifiedProgram {
275 code: Some(code_u64.to_vec()),
276 struct_access_instructions: Some(struct_access_instructions),
277 maps: Some(maps),
278 ..Default::default()
279 })
280 }
281}
282
283fn link_maps_fds(
285 current_task: &CurrentTask,
286 code: &mut Vec<EbpfInstruction>,
287) -> Result<Vec<BpfMapHandle>, Errno> {
288 let code_len = code.len();
289 let mut maps = Vec::<BpfMapHandle>::new();
290 for (pc, instruction) in code.iter_mut().enumerate() {
291 if instruction.code() == BPF_LDDW {
292 if pc >= code_len - 1 {
294 return error!(EINVAL);
295 }
296
297 match instruction.src_reg() {
298 0 => {}
299 BPF_PSEUDO_MAP_FD | BPF_PSEUDO_MAP_VALUE => {
300 let lddw_type = if instruction.src_reg() == BPF_PSEUDO_MAP_FD {
301 BPF_PSEUDO_MAP_IDX
302 } else {
303 BPF_PSEUDO_MAP_IDX_VALUE
304 };
305 instruction.set_src_reg(lddw_type);
308
309 let fd = FdNumber::from_raw(instruction.imm());
310 let object = get_bpf_object(current_task, fd)?;
311 let map: &BpfMapHandle = object.as_map()?;
312
313 let maybe_index = maps.iter().position(|v| Arc::ptr_eq(v, map));
315 let index = match maybe_index {
316 Some(index) => index,
317 None => {
318 let index = maps.len();
319 maps.push(map.clone());
320 index
321 }
322 };
323
324 instruction.set_imm(index.try_into().unwrap());
325 }
326 BPF_PSEUDO_MAP_IDX
327 | BPF_PSEUDO_MAP_IDX_VALUE
328 | BPF_PSEUDO_BTF_ID
329 | BPF_PSEUDO_FUNC => {
330 track_stub!(
331 TODO("https://fxbug.dev/378564467"),
332 "unsupported pseudo src for ldimm64",
333 instruction.src_reg()
334 );
335 return error!(ENOTSUP);
336 }
337 _ => {
338 return error!(EINVAL);
339 }
340 }
341 }
342 }
343 Ok(maps)
344}
345
346struct BufferVeriferLogger<'a> {
347 buffer: &'a mut dyn OutputBuffer,
348 full: bool,
349}
350
351impl BufferVeriferLogger<'_> {
352 fn new<'a>(buffer: &'a mut dyn OutputBuffer) -> BufferVeriferLogger<'a> {
353 BufferVeriferLogger { buffer, full: false }
354 }
355}
356
357impl VerifierLogger for BufferVeriferLogger<'_> {
358 fn log(&mut self, line: &[u8]) {
359 debug_assert!(line.is_ascii());
360
361 if self.full {
362 return;
363 }
364 if line.len() + 1 > self.buffer.available() {
365 self.full = true;
366 return;
367 }
368 match self.buffer.write(line) {
369 Err(e) => {
370 log_warn!("Unable to write verifier log: {e:?}");
371 self.full = true;
372 }
373 _ => {}
374 }
375 match self.buffer.write(b"\n") {
376 Err(e) => {
377 log_warn!("Unable to write verifier log: {e:?}");
378 self.full = true;
379 }
380 _ => {}
381 }
382 }
383}