Skip to main content

ebpf/
verifier.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![allow(unused_variables)]
6
7use crate::scalar_value::{ScalarValueData, U32Range, U32ScalarValueData, U64Range};
8use crate::visitor::{BpfVisitor, ProgramCounter, Register, Source};
9use crate::{
10    BPF_LDDW, BPF_MAX_INSTS, BPF_PSEUDO_MAP_IDX, BPF_PSEUDO_MAP_IDX_VALUE, BPF_STACK_SIZE,
11    DataWidth, EbpfError, EbpfInstruction, GENERAL_REGISTER_COUNT, MapSchema, REGISTER_COUNT,
12};
13use byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian};
14use fuchsia_sync::Mutex;
15use linux_uapi::{bpf_map_type, bpf_map_type_BPF_MAP_TYPE_ARRAY};
16use std::cmp::Ordering;
17use std::collections::{BTreeMap, HashMap, HashSet};
18use std::sync::Arc;
19use zerocopy::IntoBytes;
20
21const U32_MAX: u64 = u32::MAX as u64;
22
23/// A trait to receive the log from the verifier.
24pub trait VerifierLogger {
25    /// Log a line. The line is always a correct encoded ASCII string.
26    fn log(&mut self, line: &[u8]);
27}
28
29/// A `VerifierLogger` that drops all its content.
30pub struct NullVerifierLogger;
31
32impl VerifierLogger for NullVerifierLogger {
33    fn log(&mut self, line: &[u8]) {
34        debug_assert!(line.is_ascii());
35    }
36}
37
38/// An identifier for a memory buffer accessible by an ebpf program. The identifiers are built as a
39/// chain of unique identifier so that a buffer can contain multiple pointers to the same type and
40/// the verifier can distinguish between the different instances.
41/// The namespace of a `MemoryId`, used to prevent collisions between different
42/// sources of identifiers.
43#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Ord, PartialOrd)]
44pub enum Namespace {
45    /// For static structure identifiers (e.g., `SkBuff`, `BpfSock`) defined in `program_type.rs`.
46    Hardcoded,
47    /// For IDs generated by `MemoryId::new()` using the global counter.
48    Generated,
49    /// For IDs generated by `VerificationContext::next_id()` using the local counter.
50    Verification,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
54pub struct MemoryId {
55    namespace: Namespace,
56    id: u64,
57    parent: Option<Box<MemoryId>>,
58}
59
60impl MemoryId {
61    pub fn id(&self) -> u64 {
62        self.id
63    }
64}
65
66impl From<u64> for MemoryId {
67    fn from(id: u64) -> Self {
68        Self { namespace: Namespace::Hardcoded, id, parent: None }
69    }
70}
71
72/// A counter that allows to generate new ids for parameters. The namespace is the same as for id
73/// generated for types while verifying an ebpf program. Because different sources of identifiers
74/// use different namespaces, this counter can start at 0.
75static BPF_TYPE_IDENTIFIER_COUNTER: std::sync::atomic::AtomicU64 =
76    std::sync::atomic::AtomicU64::new(0);
77
78impl MemoryId {
79    /// Build a `MemoryId` from a raw u64.
80    ///
81    /// This is supposed to be used by the `ebpf_api` crate to build static identifiers (e.g.
82    /// for hardcoded program contexts like `SkBuff`), but not elsewhere.
83    pub const fn from_raw(id: u64) -> MemoryId {
84        Self { namespace: Namespace::Hardcoded, id, parent: None }
85    }
86
87    pub fn new() -> MemoryId {
88        Self {
89            namespace: Namespace::Generated,
90            id: BPF_TYPE_IDENTIFIER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
91            parent: None,
92        }
93    }
94
95    /// Build a new id such that `other` is prepended to the chain of parent of `self`.
96    fn prepended(&self, other: MemoryId) -> Self {
97        match &self.parent {
98            None => {
99                MemoryId { namespace: self.namespace, id: self.id, parent: Some(Box::new(other)) }
100            }
101            Some(parent) => MemoryId {
102                namespace: self.namespace,
103                id: self.id,
104                parent: Some(Box::new(parent.prepended(other))),
105            },
106        }
107    }
108
109    /// Returns whether the parent of `self` is `parent`
110    fn has_parent(&self, parent: &MemoryId) -> bool {
111        match &self.parent {
112            None => false,
113            Some(p) => p.as_ref() == parent,
114        }
115    }
116
117    /// Returns whether `self` and `other` represent the same type. This is true if both have the
118    /// same id and if they both have parent, parents must also match.
119    fn matches(&self, other: &MemoryId) -> bool {
120        if self.namespace != other.namespace || self.id != other.id {
121            return false;
122        };
123        match (&self.parent, &other.parent) {
124            (Some(p1), Some(p2)) => p1.matches(p2.as_ref()),
125            _ => true,
126        }
127    }
128}
129
130/// The type of a field in a struct pointed by `Type::PtrToStruct`.
131#[derive(Clone, Debug, PartialEq)]
132pub enum FieldType {
133    /// Read-only scalar value.
134    Scalar { size: usize },
135
136    /// Mutable scalar value.
137    MutableScalar { size: usize },
138
139    /// A pointer to the kernel memory. The full buffer is `buffer_size` bytes long.
140    PtrToMemory { is_32_bit: bool, id: MemoryId, buffer_size: usize },
141
142    /// A nullable pointer to the kernel memory. The full buffer is `buffer_size` bytes long.
143    NullablePtrToMemory { is_32_bit: bool, id: MemoryId, buffer_size: usize },
144
145    /// A pointer to the kernel memory. The full buffer size is determined by an instance of
146    /// `PtrToEndArray` with the same `id`.
147    PtrToArray { is_32_bit: bool, id: MemoryId },
148
149    /// A pointer to the kernel memory that represents the first non accessible byte of a
150    /// `PtrToArray` with the same `id`.
151    PtrToEndArray { is_32_bit: bool, id: MemoryId },
152}
153
154/// Definition of a field in a `Type::PtrToStruct`.
155#[derive(Clone, Debug, PartialEq)]
156pub struct FieldDescriptor {
157    /// The offset at which the field is located.
158    pub offset: usize,
159
160    /// The type of the pointed memory. Currently the verifier supports only `PtrToArray`,
161    /// `PtrToStruct`, `PtrToEndArray` and `ScalarValue`.
162    pub field_type: FieldType,
163}
164
165impl FieldDescriptor {
166    fn size(&self) -> usize {
167        match self.field_type {
168            FieldType::Scalar { size } | FieldType::MutableScalar { size } => size,
169            FieldType::PtrToMemory { is_32_bit, .. }
170            | FieldType::NullablePtrToMemory { is_32_bit, .. }
171            | FieldType::PtrToArray { is_32_bit, .. }
172            | FieldType::PtrToEndArray { is_32_bit, .. } => {
173                if is_32_bit {
174                    4
175                } else {
176                    8
177                }
178            }
179        }
180    }
181}
182
183/// The offset and width of a field in a struct.
184#[derive(Clone, Copy, Debug, Eq, PartialEq)]
185struct Field {
186    offset: i16,
187    width: DataWidth,
188}
189
190impl Field {
191    fn new(offset: i16, width: DataWidth) -> Self {
192        Self { offset, width }
193    }
194}
195
196/// Defines field layout in a struct pointed by `Type::PtrToStruct`.
197#[derive(Debug, PartialEq, Default)]
198pub struct StructDescriptor {
199    /// The list of fields.
200    pub fields: Vec<FieldDescriptor>,
201}
202
203impl StructDescriptor {
204    /// Return true if `self` is a subtype of `super_struct`.
205    fn is_subtype(&self, super_struct: &StructDescriptor) -> bool {
206        // Check that `super_struct.fields` is a subset of `fields`.
207        for super_field in super_struct.fields.iter() {
208            if self.fields.iter().find(|field| *field == super_field).is_none() {
209                return false;
210            }
211        }
212        true
213    }
214
215    /// Finds the field type for load/store at the specified location. None is returned if the
216    /// access is invalid and the program must be rejected.
217    fn find_field(&self, base_offset: ScalarValueData, field: Field) -> Option<&FieldDescriptor> {
218        let offset = base_offset + field.offset;
219        let field_desc = self.fields.iter().find(|f| {
220            f.offset <= offset.min() as usize && (offset.max() as usize) < f.offset + f.size()
221        })?;
222        let is_valid_load = match field_desc.field_type {
223            FieldType::Scalar { size } | FieldType::MutableScalar { size } => {
224                // For scalars check that the access is within the bounds of the field.
225                ((offset + field.width.bytes()).max() as usize) <= field_desc.offset + size
226            }
227            FieldType::PtrToMemory { is_32_bit, .. }
228            | FieldType::NullablePtrToMemory { is_32_bit, .. }
229            | FieldType::PtrToArray { is_32_bit, .. }
230            | FieldType::PtrToEndArray { is_32_bit, .. } => {
231                let expected_width = if is_32_bit { DataWidth::U32 } else { DataWidth::U64 };
232                // Pointer loads are expected to load the whole field.
233                offset.is_known()
234                    && offset.value as usize == field_desc.offset
235                    && field.width == expected_width
236            }
237        };
238
239        is_valid_load.then_some(field_desc)
240    }
241}
242
243#[derive(Clone, Debug, PartialEq)]
244pub enum MemoryParameterSize {
245    /// The memory buffer have the given size.
246    Value(u64),
247    /// The memory buffer size is given by the parameter in the given index.
248    Reference { index: u8 },
249}
250
251impl MemoryParameterSize {
252    fn size(&self, context: &ComputationContext) -> Result<u64, String> {
253        match self {
254            Self::Value(size) => Ok(*size),
255            Self::Reference { index } => {
256                let size_type = context.reg(index + 1)?;
257                match size_type {
258                    Type::ScalarValue(data) if data.is_known() => Ok(data.value),
259                    _ => Err("cannot know buffer size".to_string()),
260                }
261            }
262        }
263    }
264}
265
266#[derive(Clone, Debug, PartialEq)]
267pub enum MapTypeFilter {
268    AllowList(&'static [bpf_map_type]),
269    DenyList(&'static [bpf_map_type]),
270}
271
272impl MapTypeFilter {
273    pub fn is_allowed(&self, map_type: bpf_map_type) -> bool {
274        match self {
275            MapTypeFilter::AllowList(types) => types.contains(&map_type),
276            MapTypeFilter::DenyList(types) => !types.contains(&map_type),
277        }
278    }
279}
280
281#[derive(Clone, Debug, PartialEq)]
282pub enum Type {
283    /// A number.
284    ScalarValue(ScalarValueData),
285    /// A pointer to a map object.
286    ConstPtrToMap { id: u64, schema: MapSchema },
287    /// A pointer into the stack.
288    PtrToStack { offset: StackOffset },
289    /// A pointer to the kernel memory. The full buffer is `buffer_size` bytes long. The pointer is
290    /// situated at `offset` from the start of the buffer.
291    PtrToMemory { id: MemoryId, offset: ScalarValueData, buffer_size: u64 },
292    /// A pointer to a struct with the specified `StructDescriptor`.
293    PtrToStruct { id: MemoryId, offset: ScalarValueData, descriptor: Arc<StructDescriptor> },
294    /// A pointer to the kernel memory. The full buffer size is determined by an instance of
295    /// `PtrToEndArray` with the same `id`. The pointer is situadted at `offset` from the start of
296    /// the buffer.
297    PtrToArray { id: MemoryId, offset: ScalarValueData },
298    /// A pointer to the kernel memory that represents the first non accessible byte of a
299    /// `PtrToArray` with the same `id`.
300    PtrToEndArray { id: MemoryId },
301    /// A pointer that might be null and must be validated before being referenced.
302    NullOr { id: MemoryId, inner: Box<Type> },
303    /// An object that must be passed to a method with an associated `ReleaseParameter` before the
304    /// end of the program.
305    Releasable { id: MemoryId, inner: Box<Type> },
306    /// A function parameter that must be a `ScalarValue` when called.
307    ScalarValueParameter,
308    /// A function parameter that must be a `ConstPtrToMap` when called.
309    ConstPtrToMapParameter { filter: MapTypeFilter },
310    /// A function parameter that must be a key of a map.
311    MapKeyParameter {
312        /// The index in the arguments list that contains a `ConstPtrToMap` for the map this key is
313        /// associated with.
314        map_ptr_index: u8,
315    },
316    /// A function parameter that must be a value of a map.
317    MapValueParameter {
318        /// The index in the arguments list that contains a `ConstPtrToMap` for the map this key is
319        /// associated with.
320        map_ptr_index: u8,
321    },
322    /// A function parameter that must be a pointer to memory.
323    MemoryParameter {
324        /// The index in the arguments list that contains a scalar value containing the size of the
325        /// memory.
326        size: MemoryParameterSize,
327        /// Whether this memory is read by the function.
328        input: bool,
329        /// Whether this memory is written by the function.
330        output: bool,
331    },
332    /// A function return value that is the same type as a parameter.
333    AliasParameter {
334        /// The index in the argument list of the parameter that has the type of this return value.
335        parameter_index: u8,
336    },
337    /// A function return value that is either null, or the given type.
338    NullOrParameter(Box<Type>),
339    /// A function parameter that must be a pointer to memory with the given id.
340    StructParameter { id: MemoryId },
341    /// The parameter should be the same as the specified argument passed to the program.
342    ContextParameter { parameter_index: u8 },
343    /// A function return value that must be passed to a method with an associated
344    /// `ReleaseParameter` before the end of the program.
345    ReleasableParameter { id: MemoryId, inner: Box<Type> },
346    /// A function parameter that will release the value.
347    ReleaseParameter { id: MemoryId },
348    /// A function parameter that is not checked.
349    AnyParameter,
350}
351
352/// Defines a partial ordering on `Type` instances, capturing the notion of how "broad"
353/// a type is in terms of the set of potential values it represents.
354///
355/// The ordering is defined such that `t1 > t2` if a proof that an eBPF program terminates
356/// in a state where a register or memory location has type `t1` is also a proof that
357/// the program terminates in a state where that location has type `t2`.
358///
359/// In other words, a "broader" type represents a larger set of possible values, and
360/// proving termination with a broader type implies termination with any narrower type.
361///
362/// Examples:
363/// * `Type::ScalarValue(ScalarValueData({ unknown_mask: 0, .. }))` (a known scalar value) is less than
364///   `Type::ScalarValue(ScalarValueData({ unknown_mask: u64::MAX, .. }))` (an unknown scalar value).
365impl PartialOrd for Type {
366    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
367        // If the values are equals, return the known result.
368        if self == other {
369            return Some(Ordering::Equal);
370        }
371
372        // If one value is not initialized, the types are ordered.
373        // UNINITIALIZED is treated as the Top element (Greater than all other types).
374        // If a path is proven safe when a register is UNINITIALIZED, it means the path
375        // does not read that register. Consequently, it is safe to prune any state
376        // that has a more specific type (like a pointer) in that register, because
377        // the program will still not read it.
378        if self == &Type::UNINITIALIZED {
379            return Some(Ordering::Greater);
380        }
381        if other == &Type::UNINITIALIZED {
382            return Some(Ordering::Less);
383        }
384
385        // Otherwise, only scalars are comparables.
386        match (self, other) {
387            (Self::ScalarValue(data1), Self::ScalarValue(data2)) => data1.partial_cmp(data2),
388            _ => None,
389        }
390    }
391}
392
393impl From<ScalarValueData> for Type {
394    fn from(value: ScalarValueData) -> Self {
395        Self::ScalarValue(value)
396    }
397}
398
399impl From<u64> for Type {
400    fn from(value: u64) -> Self {
401        Self::ScalarValue(value.into())
402    }
403}
404
405impl Default for Type {
406    /// A new instance of `Type` where no bit has been written yet.
407    fn default() -> Self {
408        Self::UNINITIALIZED.clone()
409    }
410}
411
412impl Type {
413    /// An uninitialized value.
414    pub const UNINITIALIZED: Self = Self::ScalarValue(ScalarValueData::UNINITIALIZED);
415
416    /// A `Type` where the data is usable by userspace, but the value is not known by the verifier.
417    pub const UNKNOWN_SCALAR: Self = Self::ScalarValue(ScalarValueData::UNKNOWN_WRITTEN);
418
419    /// The mask associated with a data of size `width`.
420    fn mask(width: DataWidth) -> u64 {
421        if width == DataWidth::U64 { u64::MAX } else { (1 << width.bits()) - 1 }
422    }
423
424    /// Return true if `self` is a scalar with all bytes initialized.
425    fn is_written_scalar(&self) -> bool {
426        match self {
427            Self::ScalarValue(data) if data.is_fully_initialized() => true,
428            _ => false,
429        }
430    }
431
432    /// Return true if `self` has all bytes initialized.
433    fn is_initialized(&self) -> bool {
434        match self {
435            Self::ScalarValue(data) => data.is_fully_initialized(),
436            _ => true,
437        }
438    }
439
440    /// Return true if `self` is a subtype of `super_type`.
441    pub fn is_subtype(&self, super_type: &Type) -> bool {
442        match (self, super_type) {
443            // Anything can be used in place of an uninitialized value.
444            (_, Self::ScalarValue(data)) if data.is_uninitialized() => true,
445
446            (
447                Self::PtrToStruct { id: id1, offset: offset1, descriptor: descriptor1 },
448                Self::PtrToStruct { id: id2, offset: offset2, descriptor: descriptor2 },
449            ) => id1 == id2 && offset1 <= offset2 && descriptor1.is_subtype(descriptor2),
450
451            // Every type is a subtype of itself.
452            (self_type, super_type) if self_type == super_type => true,
453
454            _ => false,
455        }
456    }
457
458    /// Return true is `self` is guaranteed to be non-zero
459    pub fn is_non_zero(&self) -> bool {
460        match self {
461            Self::ScalarValue(d) => d.min() > 0,
462            Self::NullOr { .. } => false,
463            _ => true,
464        }
465    }
466
467    fn inner(&self, context: &ComputationContext) -> Result<&Type, String> {
468        match self {
469            Self::Releasable { id, inner } => {
470                if context.resources.contains(id) {
471                    Ok(&inner)
472                } else {
473                    Err("Access to released resource".to_string())
474                }
475            }
476            _ => Ok(self),
477        }
478    }
479
480    /// Constraints `type1` and `type2` for a conditional jump with the specified `jump_type` and
481    /// `jump_width`.
482    fn constraint(
483        context: &mut ComputationContext,
484        jump_type: JumpType,
485        jump_width: JumpWidth,
486        type1: Self,
487        type2: Self,
488    ) -> Result<(Self, Self), String> {
489        let result = match (jump_width, jump_type, type1.inner(context)?, type2.inner(context)?) {
490            (JumpWidth::W64, JumpType::Eq, Type::ScalarValue(data1), Type::ScalarValue(data2))
491                if data1.is_fully_initialized() && data2.is_fully_initialized() =>
492            {
493                let umin = std::cmp::max(data1.min(), data2.min());
494                let umax = std::cmp::min(data1.max(), data2.max());
495                let v = Type::ScalarValue(ScalarValueData::new(
496                    data1.value | data2.value,
497                    data1.unknown_mask & data2.unknown_mask,
498                    0,
499                    U64Range::new(umin, umax),
500                ));
501                (v.clone(), v)
502            }
503            (JumpWidth::W32, JumpType::Eq, Type::ScalarValue(data1), Type::ScalarValue(data2))
504                if data1.is_fully_initialized() && data2.is_fully_initialized() =>
505            {
506                // Refine ranges only when both operands fit in 32 bits; a range
507                // straddling 2^32 can have low bits below its min, so refining would
508                // drop reachable values.
509                let both_fit_u32 = data1.max() <= U32_MAX && data2.max() <= U32_MAX;
510                let (urange1, urange2) = if both_fit_u32 {
511                    let umin = std::cmp::max(data1.min(), data2.min());
512                    let umax = std::cmp::min(data1.max(), data2.max());
513                    let urange = U64Range::new(umin, umax);
514                    (urange, urange)
515                } else {
516                    (data1.urange, data2.urange)
517                };
518                let v1 = Type::ScalarValue(ScalarValueData::new(
519                    data1.value | (data2.value & U32_MAX),
520                    data1.unknown_mask & (data2.unknown_mask | (U32_MAX << 32)),
521                    0,
522                    urange1,
523                ));
524                let v2 = Type::ScalarValue(ScalarValueData::new(
525                    data2.value | (data1.value & U32_MAX),
526                    data2.unknown_mask & (data1.unknown_mask | (U32_MAX << 32)),
527                    0,
528                    urange2,
529                ));
530                (v1, v2)
531            }
532            (JumpWidth::W64, JumpType::Eq, Type::ScalarValue(data), Type::NullOr { id, .. })
533            | (JumpWidth::W64, JumpType::Eq, Type::NullOr { id, .. }, Type::ScalarValue(data))
534                if data.is_zero() =>
535            {
536                context.set_null(id, true);
537                let zero = Type::from(0);
538                (zero.clone(), zero)
539            }
540            (JumpWidth::W64, jump_type, Type::NullOr { id, inner }, Type::ScalarValue(data))
541                if jump_type.is_strict() && data.is_zero() =>
542            {
543                context.set_null(id, false);
544                let inner = *inner.clone();
545                inner.register_resource(context);
546                (inner, type2)
547            }
548            (JumpWidth::W64, jump_type, Type::ScalarValue(data), Type::NullOr { id, inner })
549                if jump_type.is_strict() && data.is_zero() =>
550            {
551                context.set_null(id, false);
552                let inner = *inner.clone();
553                inner.register_resource(context);
554                (type1, inner)
555            }
556
557            (JumpWidth::W64, JumpType::Lt, Type::ScalarValue(lhs), Type::ScalarValue(rhs)) => {
558                debug_assert!(lhs.min() < u64::MAX);
559                debug_assert!(rhs.max() > 0);
560                let new_max_lhs = std::cmp::min(lhs.max(), rhs.max() - 1);
561                debug_assert!(lhs.min() <= new_max_lhs);
562                let new_min_rhs = std::cmp::max(rhs.min(), lhs.min() + 1);
563                debug_assert!(new_min_rhs <= rhs.max());
564                let new_range_lhs = U64Range::new(lhs.min(), new_max_lhs);
565                let new_range_rhs = U64Range::new(new_min_rhs, rhs.max());
566                (lhs.update_range(new_range_lhs).into(), rhs.update_range(new_range_rhs).into())
567            }
568            (JumpWidth::W64, JumpType::Gt, Type::ScalarValue(lhs), Type::ScalarValue(rhs)) => {
569                debug_assert!(rhs.min() < u64::MAX);
570                debug_assert!(lhs.max() > 0);
571                let new_min_lhs = std::cmp::max(lhs.min(), rhs.min() + 1);
572                debug_assert!(new_min_lhs <= lhs.max());
573                let new_max_rhs = std::cmp::min(rhs.max(), lhs.max() - 1);
574                debug_assert!(rhs.min() <= new_max_rhs);
575                let new_range_lhs = U64Range::new(new_min_lhs, lhs.max());
576                let new_range_rhs = U64Range::new(rhs.min(), new_max_rhs);
577                (lhs.update_range(new_range_lhs).into(), rhs.update_range(new_range_rhs).into())
578            }
579
580            (JumpWidth::W64, JumpType::Le, Type::ScalarValue(lhs), Type::ScalarValue(rhs)) => {
581                let new_max_lhs = std::cmp::min(lhs.max(), rhs.max());
582                debug_assert!(lhs.min() <= new_max_lhs);
583                let new_min_rhs = std::cmp::max(rhs.min(), lhs.min());
584                debug_assert!(new_min_rhs <= rhs.max());
585                let new_range_lhs = U64Range::new(lhs.min(), new_max_lhs);
586                let new_range_rhs = U64Range::new(new_min_rhs, rhs.max());
587                (lhs.update_range(new_range_lhs).into(), rhs.update_range(new_range_rhs).into())
588            }
589            (JumpWidth::W64, JumpType::Ge, Type::ScalarValue(lhs), Type::ScalarValue(rhs)) => {
590                let new_min_lhs = std::cmp::max(lhs.min(), rhs.min());
591                debug_assert!(new_min_lhs <= lhs.max());
592                let new_max_rhs = std::cmp::min(rhs.max(), lhs.max());
593                debug_assert!(rhs.min() <= new_max_rhs);
594                let new_range_lhs = U64Range::new(new_min_lhs, lhs.max());
595                let new_range_rhs = U64Range::new(rhs.min(), new_max_rhs);
596                (lhs.update_range(new_range_lhs).into(), rhs.update_range(new_range_rhs).into())
597            }
598
599            (
600                JumpWidth::W64,
601                JumpType::Eq,
602                Type::PtrToArray { id: id1, offset },
603                Type::PtrToEndArray { id: id2 },
604            )
605            | (
606                JumpWidth::W64,
607                JumpType::Le,
608                Type::PtrToArray { id: id1, offset },
609                Type::PtrToEndArray { id: id2 },
610            )
611            | (
612                JumpWidth::W64,
613                JumpType::Ge,
614                Type::PtrToEndArray { id: id1 },
615                Type::PtrToArray { id: id2, offset },
616            ) if id1 == id2 => {
617                context.update_array_bounds(id1.clone(), *offset);
618                (type1, type2)
619            }
620            (
621                JumpWidth::W64,
622                JumpType::Lt,
623                Type::PtrToArray { id: id1, offset },
624                Type::PtrToEndArray { id: id2 },
625            )
626            | (
627                JumpWidth::W64,
628                JumpType::Gt,
629                Type::PtrToEndArray { id: id1 },
630                Type::PtrToArray { id: id2, offset },
631            ) if id1 == id2 => {
632                context.update_array_bounds(id1.clone(), *offset + 1);
633                (type1, type2)
634            }
635            (JumpWidth::W64, JumpType::Eq, _, _) => (type1.clone(), type1),
636            _ => (type1, type2),
637        };
638        Ok(result)
639    }
640
641    fn match_parameter_type(
642        &self,
643        verification_context: &VerificationContext<'_>,
644        context: &ComputationContext,
645        helper_name: &str,
646        parameter_type: &Type,
647        index: usize,
648        next: &mut ComputationContext,
649    ) -> Result<(), String> {
650        match (parameter_type, self) {
651            (Type::NullOrParameter(t), Type::ScalarValue(data))
652                if data.is_known() && data.value == 0 =>
653            {
654                Ok(())
655            }
656            (Type::NullOrParameter(t), _) => self.match_parameter_type(
657                verification_context,
658                context,
659                helper_name,
660                t,
661                index,
662                next,
663            ),
664            (Type::ScalarValueParameter, Type::ScalarValue(data))
665                if data.is_fully_initialized() =>
666            {
667                Ok(())
668            }
669            (Type::ConstPtrToMapParameter { filter }, Type::ConstPtrToMap { schema, .. }) => {
670                let map_type = schema.map_type;
671                if !filter.is_allowed(map_type) {
672                    return Err(format!("Map type {map_type} not allowed in {helper_name}"));
673                }
674                Ok(())
675            }
676            (
677                Type::MapKeyParameter { map_ptr_index },
678                Type::PtrToMemory { offset, buffer_size, .. },
679            ) => {
680                let schema = context.get_map_schema(*map_ptr_index)?;
681                context.check_memory_access(*offset, *buffer_size, 0, schema.key_size as usize)
682            }
683            (Type::MapKeyParameter { map_ptr_index }, Type::PtrToStack { offset }) => {
684                let schema = context.get_map_schema(*map_ptr_index)?;
685                context.stack.read_data_ptr(context.pc, *offset, schema.key_size as u64)
686            }
687            (
688                Type::MapValueParameter { map_ptr_index },
689                Type::PtrToMemory { offset, buffer_size, .. },
690            ) => {
691                let schema = context.get_map_schema(*map_ptr_index)?;
692                context.check_memory_access(*offset, *buffer_size, 0, schema.value_size as usize)
693            }
694            (Type::MapValueParameter { map_ptr_index }, Type::PtrToStack { offset }) => {
695                let schema = context.get_map_schema(*map_ptr_index)?;
696                context.stack.read_data_ptr(context.pc, *offset, schema.value_size as u64)
697            }
698            (Type::MemoryParameter { size, .. }, Type::PtrToMemory { offset, buffer_size, .. }) => {
699                let expected_size = size.size(context)?;
700                let offset_max = offset.max();
701                if offset_max > *buffer_size {
702                    return Err("out of bound read".to_string());
703                }
704                let size_left = *buffer_size - offset_max;
705                if expected_size > size_left {
706                    return Err("out of bound read".to_string());
707                }
708                Ok(())
709            }
710
711            (Type::MemoryParameter { size, input, output }, Type::PtrToStack { offset }) => {
712                let size = size.size(context)?;
713                let buffer_end = offset.add(size);
714                if !buffer_end.is_within_stack() {
715                    Err("out of bound access".to_string())
716                } else {
717                    if *output {
718                        next.stack.write_data_ptr(context.pc, *offset, size)?;
719                    }
720                    if *input {
721                        context.stack.read_data_ptr(context.pc, *offset, size)?;
722                    }
723                    Ok(())
724                }
725            }
726            (
727                Type::StructParameter { id: id1 },
728                Type::PtrToMemory { id: id2, offset, .. }
729                | Type::PtrToStruct { id: id2, offset, .. },
730            ) if offset.is_zero() && id1.matches(id2) => Ok(()),
731            (
732                Type::ReleasableParameter { id: id1, inner: inner1 },
733                Type::Releasable { id: id2, inner: inner2 },
734            ) if id2.has_parent(id1) => {
735                if next.resources.contains(id2) {
736                    inner2.match_parameter_type(
737                        verification_context,
738                        context,
739                        helper_name,
740                        inner1,
741                        index,
742                        next,
743                    )
744                } else {
745                    Err(format!("Resource already released for index {index}"))
746                }
747            }
748            (Type::ContextParameter { parameter_index }, arg_type) => {
749                if verification_context.calling_context.args.get(*parameter_index as usize)
750                    == Some(arg_type)
751                {
752                    Ok(())
753                } else {
754                    Err(format!("Helper expects program argument {parameter_index}"))
755                }
756            }
757            (Type::ReleaseParameter { id: id1 }, Type::Releasable { id: id2, .. })
758                if id2.has_parent(id1) =>
759            {
760                if next.resources.remove(id2) {
761                    Ok(())
762                } else {
763                    Err(format!("{id2:?} Resource already released for index {index}"))
764                }
765            }
766            (_, Type::Releasable { id, inner }) => {
767                if !next.resources.contains(id) {
768                    return Err(format!("Resource already released for index {index}"));
769                }
770                inner.match_parameter_type(
771                    verification_context,
772                    context,
773                    helper_name,
774                    parameter_type,
775                    index,
776                    next,
777                )
778            }
779            (Type::AnyParameter, _) => Ok(()),
780
781            _ => Err(format!("incorrect parameter for index {index}")),
782        }
783    }
784
785    /// If this `Type` is an instance of NullOr with the given `null_id`, replace it wither either
786    /// 0 or the subtype depending on `is_null`
787    fn set_null(&mut self, null_id: &MemoryId, is_null: bool) {
788        match self {
789            Type::NullOr { id, inner } if id == null_id => {
790                if is_null {
791                    *self = Type::from(0);
792                } else {
793                    *self = *inner.clone();
794                }
795            }
796            _ => {}
797        }
798    }
799
800    fn register_resource(&self, context: &mut ComputationContext) {
801        match self {
802            Type::Releasable { id, .. } => {
803                context.resources.insert(id.clone());
804            }
805            _ => {}
806        }
807    }
808
809    /// Partially Compares two iterators of comparable items.
810    ///
811    /// This function iterates through both input iterators simultaneously and compares the corresponding elements.
812    /// The comparison continues until:
813    /// 1. Both iterators are exhausted and all elements were considered equal, in which case it returns `Some(Ordering::Equal)`.
814    /// 2. All pairs of corresponding elements that are not equal have the same ordering (`Ordering::Less` or `Ordering::Greater`), in which case it returns `Some(Ordering)` reflecting that consistent ordering.
815    /// 3. One iterator is exhausted before the other, or any comparison between elements yields `None`, or not all non-equal pairs have the same ordering, in which case it returns `None`.
816    fn compare_list<'a>(
817        mut l1: impl Iterator<Item = &'a Self>,
818        mut l2: impl Iterator<Item = &'a Self>,
819    ) -> Option<Ordering> {
820        let mut result = Ordering::Equal;
821        loop {
822            match (l1.next(), l2.next()) {
823                (None, None) => return Some(result),
824                (_, None) | (None, _) => return None,
825                (Some(v1), Some(v2)) => {
826                    result = associate_orderings(result, v1.partial_cmp(v2)?)?;
827                }
828            }
829        }
830    }
831}
832
833#[derive(Clone, Debug)]
834pub struct FunctionSignature {
835    pub args: Vec<Type>,
836    pub return_value: Type,
837    pub invalidate_array_bounds: bool,
838}
839
840#[derive(Clone, Debug)]
841pub struct HelperDefinition {
842    pub index: u32,
843    pub name: &'static str,
844    pub signature: FunctionSignature,
845}
846
847#[derive(Debug, Default)]
848pub struct CallingContext {
849    /// List of map schemas of the associated map. The maps can be accessed using LDDW instruction
850    /// with `src_reg=BPF_PSEUDO_MAP_IDX`.
851    pub maps: Vec<MapSchema>,
852    /// The registered external functions.
853    pub helpers: HashMap<u32, &'static HelperDefinition>,
854    /// The args of the program.
855    pub args: Vec<Type>,
856    /// Packet type. Normally it should be either `None` or `args[0]`.
857    pub packet_type: Option<Type>,
858}
859
860impl CallingContext {
861    pub fn register_map(&mut self, schema: MapSchema) -> usize {
862        let index = self.maps.len();
863        self.maps.push(schema);
864        index
865    }
866}
867
868#[derive(Debug, PartialEq, Clone)]
869pub struct StructAccess {
870    pub pc: ProgramCounter,
871
872    // Memory Id of the struct being accessed.
873    pub memory_id: MemoryId,
874
875    // Offset of the field being loaded.
876    pub field_offset: usize,
877
878    // Indicates that this is a 32-bit pointer load. These instructions must be remapped.
879    pub is_32_bit_ptr_load: bool,
880}
881
882#[derive(Debug, Clone)]
883pub struct VerifiedEbpfProgram {
884    pub(crate) code: Vec<EbpfInstruction>,
885    pub(crate) args: Vec<Type>,
886    pub(crate) struct_access_instructions: Vec<StructAccess>,
887    pub(crate) maps: Vec<MapSchema>,
888}
889
890impl VerifiedEbpfProgram {
891    pub fn code(&self) -> &[EbpfInstruction] {
892        &self.code
893    }
894
895    pub fn struct_access_instructions(&self) -> &[StructAccess] {
896        &self.struct_access_instructions
897    }
898
899    pub fn from_verified_code(
900        code: Vec<EbpfInstruction>,
901        args: Vec<Type>,
902        struct_access_instructions: Vec<StructAccess>,
903        maps: Vec<MapSchema>,
904    ) -> Self {
905        Self { code, args, struct_access_instructions, maps }
906    }
907
908    pub fn maps(&self) -> &[MapSchema] {
909        &self.maps
910    }
911}
912
913/// Verify the given code depending on the type of the parameters and the registered external
914/// functions. Returned `VerifiedEbpfProgram` should be linked in order to execute it.
915pub fn verify_program(
916    code: Vec<EbpfInstruction>,
917    calling_context: CallingContext,
918    logger: &mut dyn VerifierLogger,
919) -> Result<VerifiedEbpfProgram, EbpfError> {
920    if code.len() > BPF_MAX_INSTS {
921        return error_and_log(logger, "ebpf program too long");
922    }
923    // Pre-scan the program to ensure all LDDW instructions are structurally well-formed,
924    // even if they are in unreachable code. This is necessary because:
925    // 1. The linker may rewrite LDDW instructions (e.g., to resolve map pointers) and
926    //    expects them to be well-formed.
927    // 2. It ensures that the second part of every LDDW has a `code` of 0 (invalid opcode).
928    //    This guarantees that any attempt to jump directly into the second part of an
929    //    LDDW will be rejected during verification as an invalid instruction.
930    let mut scan_pc = 0;
931    while scan_pc < code.len() {
932        let inst = &code[scan_pc];
933        if inst.code() == BPF_LDDW {
934            let Some(next_instruction) = code.get(scan_pc + 1) else {
935                return error_and_log(logger, "incomplete lddw");
936            };
937            if next_instruction.code() != 0
938                || next_instruction.offset() != 0
939                || next_instruction.src_reg() != 0
940                || next_instruction.dst_reg() != 0
941            {
942                return error_and_log(logger, "invalid lddw");
943            }
944            scan_pc += 2;
945        } else {
946            scan_pc += 1;
947        }
948    }
949
950    let mut context = ComputationContext::default();
951    for (i, t) in calling_context.args.iter().enumerate() {
952        // The parameter registers are r1 to r5.
953        context.set_reg((i + 1) as u8, t.clone()).map_err(EbpfError::ProgramVerifyError)?;
954    }
955    let states = vec![context];
956    let mut verification_context = VerificationContext::new(calling_context, logger, &code, states);
957    while let Some(mut context) = verification_context.states.pop() {
958        if let Some(terminating_contexts) =
959            verification_context.terminating_contexts.get(context.pc)
960        {
961            // Check whether there exist a context that terminate and prove that this context does
962            // also terminate.
963            if let Some(ending_context) =
964                terminating_contexts.iter().rev().find(|c| c.computation_context >= context)
965            {
966                // One such context has been found, this proves the current context terminates.
967                // If the context has a parent, register the data dependencies and try to terminate
968                // it.
969                if let Some(parent) = context.parent.take() {
970                    parent.dependencies.lock().merge(ending_context.dependencies);
971                    if let Some(parent) = Arc::into_inner(parent) {
972                        parent
973                            .terminate(&mut verification_context)
974                            .map_err(EbpfError::ProgramVerifyError)?;
975                    }
976                }
977                continue;
978            }
979        }
980        if verification_context.iteration > 10 * BPF_MAX_INSTS {
981            return error_and_log(verification_context.logger, "bpf byte code does not terminate");
982        }
983        if context.pc >= code.len() {
984            return error_and_log(verification_context.logger, "pc out of bounds");
985        }
986        if let Err(message) = context.visit(&mut verification_context, code[context.pc]) {
987            let message = format!("at PC {}: {}", context.pc, message);
988            return error_and_log(verification_context.logger, message);
989        }
990        if context.terminated {
991            context.terminate(&mut verification_context).map_err(EbpfError::ProgramVerifyError)?;
992        }
993        verification_context.iteration += 1;
994    }
995
996    let struct_access_instructions =
997        verification_context.struct_access_instructions.into_values().collect::<Vec<_>>();
998    let CallingContext { maps, args, .. } = verification_context.calling_context;
999    Ok(VerifiedEbpfProgram { code, struct_access_instructions, maps, args })
1000}
1001
1002struct VerificationContext<'a> {
1003    /// The type information for the program arguments and the registered functions.
1004    calling_context: CallingContext,
1005    /// The logger to use.
1006    logger: &'a mut dyn VerifierLogger,
1007    /// The `ComputationContext` yet to be validated.
1008    states: Vec<ComputationContext>,
1009    /// The program being analyzed.
1010    code: &'a [EbpfInstruction],
1011    /// A counter used to generated unique ids for memory buffers and maps.
1012    counter: u64,
1013    /// The current iteration of the verifier. Used to ensure termination by limiting the number of
1014    /// iteration before bailing out.
1015    iteration: usize,
1016    /// Keep track of the context that terminates at a given pc. The list of context will all be
1017    /// incomparables as each time a bigger context is computed, the smaller ones are removed from
1018    /// the list. Indexed directly by `ProgramCounter` (`0..code.len()`) for O(1) lookup.
1019    terminating_contexts: Vec<Vec<TerminatingContext>>,
1020    /// The current set of struct access instructions that will need to be updated when the
1021    /// program is linked. This is also used to ensure that a given instruction always loads the
1022    /// same field. If this is not the case, the verifier will reject the program.
1023    struct_access_instructions: HashMap<ProgramCounter, StructAccess>,
1024}
1025
1026impl<'a> VerificationContext<'a> {
1027    fn new(
1028        calling_context: CallingContext,
1029        logger: &'a mut dyn VerifierLogger,
1030        code: &'a [EbpfInstruction],
1031        states: Vec<ComputationContext>,
1032    ) -> Self {
1033        Self {
1034            calling_context,
1035            logger,
1036            states,
1037            code,
1038            counter: 0,
1039            iteration: 0,
1040            terminating_contexts: (0..code.len()).map(|_| Vec::new()).collect(),
1041            struct_access_instructions: Default::default(),
1042        }
1043    }
1044
1045    fn next_id(&mut self) -> MemoryId {
1046        let id = self.counter;
1047        self.counter += 1;
1048        MemoryId { namespace: Namespace::Verification, id, parent: None }
1049    }
1050
1051    /// Register an instruction that loads or stores a struct field. These instructions will need
1052    /// to updated later when the program is linked.
1053    fn register_struct_access(&mut self, struct_access: StructAccess) -> Result<(), String> {
1054        match self.struct_access_instructions.entry(struct_access.pc) {
1055            std::collections::hash_map::Entry::Vacant(entry) => {
1056                entry.insert(struct_access);
1057            }
1058            std::collections::hash_map::Entry::Occupied(entry) => {
1059                if *entry.get() != struct_access {
1060                    return Err("Inconsistent struct field access".to_string());
1061                }
1062            }
1063        }
1064        Ok(())
1065    }
1066}
1067
1068const STACK_ELEMENT_SIZE: usize = std::mem::size_of::<u64>();
1069
1070/// An offset inside the stack. The offset is from the end of the stack.
1071/// downward.
1072#[derive(Clone, Copy, Debug, PartialEq)]
1073pub struct StackOffset(ScalarValueData);
1074
1075impl Default for StackOffset {
1076    fn default() -> Self {
1077        Self(BPF_STACK_SIZE.into())
1078    }
1079}
1080
1081impl StackOffset {
1082    /// Whether the current offset is valid for element access.
1083    fn is_valid_offset(&self) -> bool {
1084        self.0.is_known() && self.0.value < (BPF_STACK_SIZE as u64)
1085    }
1086
1087    /// Whether the current offset is within the stack bounds, inclusive of `BPF_STACK_SIZE`.
1088    fn is_within_stack(&self) -> bool {
1089        self.0.is_known() && self.0.value <= (BPF_STACK_SIZE as u64)
1090    }
1091
1092    /// The value of the register.
1093    fn reg(&self) -> ScalarValueData {
1094        self.0
1095    }
1096
1097    /// The index into the stack array this offset points to. Can be called only if
1098    /// `is_within_stack()` is true.
1099    fn array_index(&self) -> usize {
1100        debug_assert!(self.is_within_stack());
1101        usize::try_from(self.0.value).unwrap() / STACK_ELEMENT_SIZE
1102    }
1103
1104    /// The offset inside the aligned u64 in the stack.
1105    fn sub_index(&self) -> usize {
1106        debug_assert!(self.is_within_stack());
1107        usize::try_from(self.0.value).unwrap() % STACK_ELEMENT_SIZE
1108    }
1109
1110    fn add<T: Into<ScalarValueData>>(self, rhs: T) -> Self {
1111        Self(self.0 + rhs)
1112    }
1113}
1114
1115/// The state of the stack
1116#[derive(Clone, Debug, Default, PartialEq)]
1117struct Stack {
1118    data: BTreeMap<usize, Type>,
1119}
1120
1121impl Stack {
1122    /// Replace all instances of the NullOr type with the given `null_id` to either 0 or the
1123    /// subtype depending on `is_null`
1124    fn set_null(&mut self, null_id: &MemoryId, is_null: bool) {
1125        for (_, t) in self.data.iter_mut() {
1126            t.set_null(null_id, is_null);
1127        }
1128    }
1129
1130    fn get(&self, index: usize) -> &Type {
1131        self.data.get(&index).unwrap_or(&Type::UNINITIALIZED)
1132    }
1133
1134    fn set(&mut self, index: usize, t: Type) {
1135        if t == Type::UNINITIALIZED {
1136            self.data.remove(&index);
1137        } else {
1138            self.data.insert(index, t);
1139        }
1140    }
1141
1142    fn extract_sub_value(value: u64, offset: usize, byte_count: usize) -> u64 {
1143        NativeEndian::read_uint(&value.as_bytes()[offset..], byte_count)
1144    }
1145
1146    fn insert_sub_value(mut original: u64, value: u64, width: DataWidth, offset: usize) -> u64 {
1147        let byte_count = width.bytes();
1148        let original_buf = original.as_mut_bytes();
1149        let value_buf = value.as_bytes();
1150        original_buf[offset..(byte_count + offset)].copy_from_slice(&value_buf[..byte_count]);
1151        original
1152    }
1153
1154    fn write_data_ptr(
1155        &mut self,
1156        pc: ProgramCounter,
1157        mut offset: StackOffset,
1158        bytes: u64,
1159    ) -> Result<(), String> {
1160        for i in 0..bytes {
1161            self.store(offset, Type::UNKNOWN_SCALAR, DataWidth::U8)?;
1162            offset = offset.add(1);
1163        }
1164        Ok(())
1165    }
1166
1167    fn read_data_ptr(
1168        &self,
1169        pc: ProgramCounter,
1170        offset: StackOffset,
1171        bytes: u64,
1172    ) -> Result<(), String> {
1173        let read_element =
1174            |index: usize, start_offset: usize, end_offset: usize| -> Result<(), String> {
1175                match self.get(index) {
1176                    Type::ScalarValue(data) => {
1177                        debug_assert!(end_offset > start_offset);
1178                        let unwritten_bits = Self::extract_sub_value(
1179                            data.unwritten_mask,
1180                            start_offset,
1181                            end_offset - start_offset,
1182                        );
1183                        if unwritten_bits == 0 {
1184                            Ok(())
1185                        } else {
1186                            Err("reading unwritten value from the stack".to_string())
1187                        }
1188                    }
1189                    _ => Err("invalid read from the stack".to_string()),
1190                }
1191            };
1192        if bytes == 0 {
1193            return Ok(());
1194        }
1195
1196        if bytes as usize > BPF_STACK_SIZE {
1197            return Err("stack overflow".to_string());
1198        }
1199
1200        if !offset.is_valid_offset() {
1201            return Err("invalid stack offset".to_string());
1202        }
1203
1204        let end_offset = offset.add(bytes);
1205        if !end_offset.is_within_stack() {
1206            return Err("stack overflow".to_string())?;
1207        }
1208
1209        // Handle the case where all the data is contained in a single element excluding the last
1210        // byte (the case when the read ends at an element edge, i.e. `end_offset.sub_index()==0`,
1211        // is covered by the default path below).
1212        if offset.array_index() == end_offset.array_index() {
1213            return read_element(offset.array_index(), offset.sub_index(), end_offset.sub_index());
1214        }
1215
1216        // Handle the first element, that might be partial.
1217        read_element(offset.array_index(), offset.sub_index(), STACK_ELEMENT_SIZE)?;
1218
1219        // Handle the last element, that might be partial.
1220        if end_offset.sub_index() != 0 {
1221            read_element(end_offset.array_index(), 0, end_offset.sub_index())?;
1222        }
1223
1224        // Handle the any full type between beginning and end.
1225        for i in (offset.array_index() + 1)..end_offset.array_index() {
1226            read_element(i, 0, STACK_ELEMENT_SIZE)?;
1227        }
1228
1229        Ok(())
1230    }
1231
1232    fn store(&mut self, offset: StackOffset, value: Type, width: DataWidth) -> Result<(), String> {
1233        if !offset.is_valid_offset() {
1234            return Err("out of bounds store".to_string());
1235        }
1236        if offset.sub_index() % width.bytes() != 0 {
1237            return Err("misaligned access".to_string());
1238        }
1239
1240        let index = offset.array_index();
1241        if width == DataWidth::U64 {
1242            self.set(index, value);
1243        } else {
1244            match value {
1245                Type::ScalarValue(data) => {
1246                    let old_data = match self.get(index) {
1247                        Type::ScalarValue(data) => *data,
1248                        _ => {
1249                            // The value in the stack is not a scalar. Let consider it an scalar
1250                            // value with no written bits.
1251                            ScalarValueData::UNINITIALIZED
1252                        }
1253                    };
1254                    let sub_index = offset.sub_index();
1255                    let value =
1256                        Self::insert_sub_value(old_data.value, data.value, width, sub_index);
1257                    let unknown_mask = Self::insert_sub_value(
1258                        old_data.unknown_mask,
1259                        data.unknown_mask,
1260                        width,
1261                        sub_index,
1262                    );
1263                    let unwritten_mask = Self::insert_sub_value(
1264                        old_data.unwritten_mask,
1265                        data.unwritten_mask,
1266                        width,
1267                        sub_index,
1268                    );
1269                    let urange = U64Range::compute_range_for_bytes_swap(
1270                        old_data.urange,
1271                        data.urange,
1272                        sub_index,
1273                        0,
1274                        width.bytes(),
1275                    );
1276                    self.set(
1277                        index,
1278                        Type::ScalarValue(ScalarValueData::new(
1279                            value,
1280                            unknown_mask,
1281                            unwritten_mask,
1282                            urange,
1283                        )),
1284                    );
1285                }
1286                _ => {
1287                    return Err("cannot store part of a non scalar value on the stack".to_string());
1288                }
1289            }
1290        }
1291        Ok(())
1292    }
1293
1294    fn load(&self, offset: StackOffset, width: DataWidth) -> Result<Type, String> {
1295        if !offset.is_valid_offset() {
1296            return Err("out of bounds load".to_string());
1297        }
1298        if offset.sub_index() % width.bytes() != 0 {
1299            return Err("misaligned access".to_string());
1300        }
1301
1302        let index = offset.array_index();
1303        let loaded_type = self.get(index).clone();
1304        let result = if width == DataWidth::U64 {
1305            loaded_type
1306        } else {
1307            match loaded_type {
1308                Type::ScalarValue(data) => {
1309                    let sub_index = offset.sub_index();
1310                    let value = Self::extract_sub_value(data.value, sub_index, width.bytes());
1311                    let unknown_mask =
1312                        Self::extract_sub_value(data.unknown_mask, sub_index, width.bytes());
1313                    let unwritten_mask =
1314                        Self::extract_sub_value(data.unwritten_mask, sub_index, width.bytes());
1315                    let urange = U64Range::compute_range_for_bytes_swap(
1316                        0.into(),
1317                        data.urange,
1318                        0,
1319                        sub_index,
1320                        width.bytes(),
1321                    );
1322                    Type::ScalarValue(ScalarValueData::new(
1323                        value,
1324                        unknown_mask,
1325                        unwritten_mask,
1326                        urange,
1327                    ))
1328                }
1329                _ => return Err(format!("incorrect load of {} bytes", width.bytes())),
1330            }
1331        };
1332        if !result.is_initialized() {
1333            return Err("reading unwritten value from the stack".to_string());
1334        }
1335        Ok(result)
1336    }
1337}
1338
1339/// Two types are ordered with `t1` > `t2` if a proof that a program in a state `t1` finish is also
1340/// a proof that a program in a state `t2` finish.
1341impl PartialOrd for Stack {
1342    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1343        let mut result = Ordering::Equal;
1344        let mut data_iter1 = self.data.iter().peekable();
1345        let mut data_iter2 = other.data.iter().peekable();
1346        loop {
1347            let (v1, v2) = match (data_iter1.peek(), data_iter2.peek()) {
1348                (None, None) => return Some(result),
1349                (Some((_, v1)), None) => {
1350                    let v1 = *v1;
1351                    data_iter1.next();
1352                    (v1, &Type::UNINITIALIZED)
1353                }
1354                (None, Some((_, v2))) => {
1355                    let v2 = *v2;
1356                    data_iter2.next();
1357                    (&Type::UNINITIALIZED, v2)
1358                }
1359                (Some((k1, v1)), Some((k2, v2))) => match k1.cmp(k2) {
1360                    Ordering::Less => {
1361                        let v1 = *v1;
1362                        data_iter1.next();
1363                        (v1, &Type::UNINITIALIZED)
1364                    }
1365                    Ordering::Greater => {
1366                        let v2 = *v2;
1367                        data_iter2.next();
1368                        (&Type::UNINITIALIZED, v2)
1369                    }
1370                    Ordering::Equal => {
1371                        let (v1, v2) = (*v1, *v2);
1372                        data_iter1.next();
1373                        data_iter2.next();
1374                        (v1, v2)
1375                    }
1376                },
1377            };
1378            result = associate_orderings(result, v1.partial_cmp(v2)?)?;
1379        }
1380    }
1381}
1382
1383macro_rules! bpf_log {
1384    ($context:ident, $verification_context:ident, $($msg:tt)*) => {
1385        let prefix = format!("{}: ({:02x})", $context.pc, $verification_context.code[$context.pc].code());
1386        let suffix = format!($($msg)*);
1387        $verification_context.logger.log(format!("{prefix} {suffix}").as_bytes());
1388    }
1389}
1390
1391/// The state of the computation as known by the verifier at a given point in time.
1392#[derive(Debug, Default)]
1393struct ComputationContext {
1394    /// The program counter.
1395    pc: ProgramCounter,
1396    /// Register 0 to 9.
1397    registers: [Type; GENERAL_REGISTER_COUNT as usize],
1398    /// The state of the stack.
1399    stack: Stack,
1400    /// The dynamically known bounds of buffers indexed by their ids.
1401    array_bounds: BTreeMap<MemoryId, u64>,
1402    /// The currently allocated resources.
1403    resources: HashSet<MemoryId>,
1404    /// The previous context in the computation.
1405    parent: Option<Arc<ComputationContext>>,
1406    /// The data dependencies of this context. This is used to broaden a known ending context to
1407    /// help cutting computation branches.
1408    dependencies: Mutex<DataDependencies>,
1409    /// Whether this context has reached an exit instruction. The main loop uses this flag to
1410    /// call terminate() on the owned context, avoiding the extra Arc reference that would be
1411    /// created by cloning self in the exit handler.
1412    terminated: bool,
1413}
1414
1415impl Clone for ComputationContext {
1416    fn clone(&self) -> Self {
1417        Self {
1418            pc: self.pc,
1419            registers: self.registers.clone(),
1420            stack: self.stack.clone(),
1421            array_bounds: self.array_bounds.clone(),
1422            resources: self.resources.clone(),
1423            parent: self.parent.clone(),
1424            // dependencies are erased as they must always be used on the same instance of the
1425            // context.
1426            dependencies: Default::default(),
1427            terminated: false,
1428        }
1429    }
1430}
1431
1432/// parent and dependencies are ignored for the comparison, as they do not matter for termination.
1433impl PartialEq for ComputationContext {
1434    fn eq(&self, other: &Self) -> bool {
1435        self.pc == other.pc
1436            && self.registers == other.registers
1437            && self.stack == other.stack
1438            && self.array_bounds == other.array_bounds
1439    }
1440}
1441
1442impl ComputationContext {
1443    /// Replace all instances of the NullOr type with the given `null_id` to either 0 or the
1444    /// subtype depending on `is_null`
1445    fn set_null(&mut self, null_id: &MemoryId, is_null: bool) {
1446        for i in 0..self.registers.len() {
1447            self.registers[i].set_null(null_id, is_null);
1448        }
1449        self.stack.set_null(null_id, is_null);
1450    }
1451
1452    fn reg(&self, index: Register) -> Result<Type, String> {
1453        if index >= REGISTER_COUNT {
1454            return Err(format!("R{index} is invalid"));
1455        }
1456        if index < GENERAL_REGISTER_COUNT {
1457            Ok(self.registers[index as usize].clone())
1458        } else {
1459            Ok(Type::PtrToStack { offset: StackOffset::default() })
1460        }
1461    }
1462
1463    fn set_reg(&mut self, index: Register, reg_type: Type) -> Result<(), String> {
1464        if index >= GENERAL_REGISTER_COUNT {
1465            return Err(format!("R{index} is invalid"));
1466        }
1467        self.registers[index as usize] = reg_type;
1468        Ok(())
1469    }
1470
1471    fn update_array_bounds(&mut self, id: MemoryId, new_bound: ScalarValueData) {
1472        let new_bound_min = new_bound.min();
1473        self.array_bounds
1474            .entry(id)
1475            .and_modify(|v| *v = std::cmp::max(*v, new_bound_min))
1476            .or_insert(new_bound_min);
1477    }
1478
1479    fn get_map_schema(&self, argument: u8) -> Result<MapSchema, String> {
1480        match self.reg(argument + 1)? {
1481            Type::ConstPtrToMap { schema, .. } => Ok(schema),
1482            _ => Err(format!("No map found at argument {argument}")),
1483        }
1484    }
1485
1486    fn next(&self) -> Result<Self, String> {
1487        let parent = Some(Arc::new(self.clone()));
1488        self.jump_with_offset(0, parent)
1489    }
1490
1491    /// Returns a new `ComputationContext` where the pc has jump by `offset + 1`. In particular,
1492    /// the next instruction is reached with `jump_with_offset(0)`.
1493    fn jump_with_offset(&self, offset: i16, parent: Option<Arc<Self>>) -> Result<Self, String> {
1494        let pc = self
1495            .pc
1496            .checked_add_signed(offset.into())
1497            .and_then(|v| v.checked_add_signed(1))
1498            .ok_or_else(|| "jump outside of program".to_string())?;
1499        let result = Self {
1500            pc,
1501            registers: self.registers.clone(),
1502            stack: self.stack.clone(),
1503            array_bounds: self.array_bounds.clone(),
1504            resources: self.resources.clone(),
1505            parent,
1506            dependencies: Default::default(),
1507            terminated: false,
1508        };
1509        Ok(result)
1510    }
1511
1512    fn check_memory_access(
1513        &self,
1514        dst_offset: ScalarValueData,
1515        dst_buffer_size: u64,
1516        instruction_offset: i16,
1517        width: usize,
1518    ) -> Result<(), String> {
1519        let memory_range = dst_offset.urange + instruction_offset + U64Range::new(0, width as u64);
1520        if memory_range.max > dst_buffer_size {
1521            return Err("out of bound access".to_string());
1522        }
1523        Ok(())
1524    }
1525
1526    fn store_memory(
1527        &mut self,
1528        context: &mut VerificationContext<'_>,
1529        addr: &Type,
1530        field: Field,
1531        value: Type,
1532    ) -> Result<(), String> {
1533        let addr = addr.inner(self)?;
1534        match *addr {
1535            Type::PtrToStack { offset } => {
1536                let offset_sum = offset.add(field.offset);
1537                return self.stack.store(offset_sum, value, field.width);
1538            }
1539            Type::PtrToMemory { offset, buffer_size, .. } => {
1540                self.check_memory_access(offset, buffer_size, field.offset, field.width.bytes())?;
1541            }
1542            Type::PtrToStruct { ref id, offset, ref descriptor, .. } => {
1543                let field_desc = descriptor
1544                    .find_field(offset, field)
1545                    .ok_or_else(|| "incorrect store".to_string())?;
1546
1547                if !matches!(field_desc.field_type, FieldType::MutableScalar { .. }) {
1548                    return Err("store to a read-only field".to_string());
1549                }
1550
1551                context.register_struct_access(StructAccess {
1552                    pc: self.pc,
1553                    memory_id: id.clone(),
1554                    field_offset: field_desc.offset,
1555                    is_32_bit_ptr_load: false,
1556                })?;
1557            }
1558            Type::PtrToArray { ref id, offset } => {
1559                self.check_memory_access(
1560                    offset,
1561                    *self.array_bounds.get(&id).unwrap_or(&0),
1562                    field.offset,
1563                    field.width.bytes(),
1564                )?;
1565            }
1566            _ => return Err("incorrect store".to_string()),
1567        }
1568
1569        match value {
1570            Type::ScalarValue(data) if data.is_fully_initialized() => {}
1571            // Private data should not be leaked.
1572            _ => return Err("incorrect store".to_string()),
1573        }
1574        Ok(())
1575    }
1576
1577    fn load_memory(
1578        &self,
1579        context: &mut VerificationContext<'_>,
1580        addr: &Type,
1581        field: Field,
1582    ) -> Result<Type, String> {
1583        let addr = addr.inner(self)?;
1584        match *addr {
1585            Type::PtrToStack { offset } => {
1586                let offset_sum = offset.add(field.offset);
1587                self.stack.load(offset_sum, field.width)
1588            }
1589            Type::PtrToMemory { ref id, offset, buffer_size, .. } => {
1590                self.check_memory_access(offset, buffer_size, field.offset, field.width.bytes())?;
1591                Ok(Type::UNKNOWN_SCALAR)
1592            }
1593            Type::PtrToStruct { ref id, offset, ref descriptor, .. } => {
1594                let field_desc = descriptor
1595                    .find_field(offset, field)
1596                    .ok_or_else(|| "incorrect load".to_string())?;
1597
1598                let (return_type, is_32_bit_ptr_load) = match &field_desc.field_type {
1599                    FieldType::Scalar { .. } | FieldType::MutableScalar { .. } => {
1600                        (Type::UNKNOWN_SCALAR, false)
1601                    }
1602                    FieldType::PtrToArray { id: array_id, is_32_bit } => (
1603                        Type::PtrToArray { id: array_id.prepended(id.clone()), offset: 0.into() },
1604                        *is_32_bit,
1605                    ),
1606                    FieldType::PtrToEndArray { id: array_id, is_32_bit } => {
1607                        (Type::PtrToEndArray { id: array_id.prepended(id.clone()) }, *is_32_bit)
1608                    }
1609                    FieldType::PtrToMemory { id: memory_id, buffer_size, is_32_bit } => (
1610                        Type::PtrToMemory {
1611                            id: memory_id.prepended(id.clone()),
1612                            offset: 0.into(),
1613                            buffer_size: *buffer_size as u64,
1614                        },
1615                        *is_32_bit,
1616                    ),
1617                    FieldType::NullablePtrToMemory { id: memory_id, buffer_size, is_32_bit } => {
1618                        let id = memory_id.prepended(id.clone());
1619                        (
1620                            Type::NullOr {
1621                                id: id.clone(),
1622                                inner: Box::new(Type::PtrToMemory {
1623                                    id,
1624                                    offset: 0.into(),
1625                                    buffer_size: *buffer_size as u64,
1626                                }),
1627                            },
1628                            *is_32_bit,
1629                        )
1630                    }
1631                };
1632
1633                context.register_struct_access(StructAccess {
1634                    pc: self.pc,
1635                    memory_id: id.clone(),
1636                    field_offset: field_desc.offset,
1637                    is_32_bit_ptr_load,
1638                })?;
1639
1640                Ok(return_type)
1641            }
1642            Type::PtrToArray { ref id, offset } => {
1643                self.check_memory_access(
1644                    offset,
1645                    *self.array_bounds.get(&id).unwrap_or(&0),
1646                    field.offset,
1647                    field.width.bytes(),
1648                )?;
1649                Ok(Type::UNKNOWN_SCALAR)
1650            }
1651            _ => Err("incorrect load".to_string()),
1652        }
1653    }
1654
1655    /**
1656     * Given the given `return_value` in a method signature, return the concrete type to use,
1657     * updating the `next` context is needed.
1658     *
1659     * `maybe_null` is true is the computed value will be a descendant of a `NullOr` type.
1660     */
1661    fn resolve_return_value(
1662        &self,
1663        verification_context: &mut VerificationContext<'_>,
1664        return_value: &Type,
1665        next: &mut ComputationContext,
1666        maybe_null: bool,
1667    ) -> Result<Type, String> {
1668        match return_value {
1669            Type::AliasParameter { parameter_index } => self.reg(parameter_index + 1),
1670            Type::ReleasableParameter { id, inner } => {
1671                let id = verification_context.next_id().prepended(id.clone());
1672                if !maybe_null {
1673                    next.resources.insert(id.clone());
1674                }
1675                Ok(Type::Releasable {
1676                    id,
1677                    inner: Box::new(self.resolve_return_value(
1678                        verification_context,
1679                        inner,
1680                        next,
1681                        maybe_null,
1682                    )?),
1683                })
1684            }
1685            Type::NullOrParameter(t) => {
1686                let id = verification_context.next_id();
1687                Ok(Type::NullOr {
1688                    id,
1689                    inner: Box::new(self.resolve_return_value(
1690                        verification_context,
1691                        t,
1692                        next,
1693                        true,
1694                    )?),
1695                })
1696            }
1697            Type::MapValueParameter { map_ptr_index } => {
1698                let schema = self.get_map_schema(*map_ptr_index)?;
1699                let id = verification_context.next_id();
1700                Ok(Type::PtrToMemory {
1701                    id,
1702                    offset: 0.into(),
1703                    buffer_size: schema.value_size as u64,
1704                })
1705            }
1706            Type::MemoryParameter { size, .. } => {
1707                let buffer_size = size.size(self)?;
1708                let id = verification_context.next_id();
1709                Ok(Type::PtrToMemory { id, offset: 0.into(), buffer_size })
1710            }
1711            Type::PtrToMemory { id, offset, buffer_size } => {
1712                let id = id.prepended(verification_context.next_id());
1713                Ok(Type::PtrToMemory { id, offset: *offset, buffer_size: *buffer_size })
1714            }
1715            Type::PtrToStruct { id, offset, descriptor } => {
1716                let id = id.prepended(verification_context.next_id());
1717                Ok(Type::PtrToStruct { id, offset: *offset, descriptor: descriptor.clone() })
1718            }
1719            t => Ok(t.clone()),
1720        }
1721    }
1722
1723    fn compute_source(&self, src: Source) -> Result<Type, String> {
1724        match src {
1725            Source::Reg(reg) => self.reg(reg),
1726            Source::Value(v) => Ok(v.into()),
1727        }
1728    }
1729
1730    fn apply_computation(
1731        context: &ComputationContext,
1732        op1: Type,
1733        op2: Type,
1734        alu_type: AluType,
1735        op: impl Fn(ScalarValueData, ScalarValueData) -> ScalarValueData,
1736    ) -> Result<Type, String> {
1737        let result: Type = match (alu_type, op1.inner(context)?, op2.inner(context)?) {
1738            (_, Type::ScalarValue(data1), Type::ScalarValue(data2)) => op(*data1, *data2).into(),
1739            (
1740                AluType::Add,
1741                Type::ScalarValue(_),
1742                Type::PtrToStack { .. } | Type::PtrToMemory { .. } | Type::PtrToStruct { .. },
1743            ) => {
1744                return Self::apply_computation(context, op2, op1, alu_type, op);
1745            }
1746            (alu_type, Type::PtrToStack { offset: x }, Type::ScalarValue(data))
1747                if alu_type.is_ptr_compatible() =>
1748            {
1749                Type::PtrToStack { offset: run_on_stack_offset(*x, |x| op(x, *data)) }
1750            }
1751            (
1752                alu_type,
1753                Type::PtrToMemory { id, offset: x, buffer_size },
1754                Type::ScalarValue(data),
1755            ) if alu_type.is_ptr_compatible() => {
1756                let offset = op(*x, *data);
1757                Type::PtrToMemory { id: id.clone(), offset, buffer_size: *buffer_size }
1758            }
1759            (
1760                alu_type,
1761                Type::PtrToStruct { id, offset: x, descriptor },
1762                Type::ScalarValue(data),
1763            ) if alu_type.is_ptr_compatible() => {
1764                let offset = op(*x, *data);
1765                Type::PtrToStruct { id: id.clone(), offset, descriptor: descriptor.clone() }
1766            }
1767            (AluType::Add, Type::PtrToArray { id, offset: x }, Type::ScalarValue(data)) => {
1768                let offset = x.checked_add(*data).ok_or_else(|| format!("XXX"))?;
1769                Type::PtrToArray { id: id.clone(), offset }
1770            }
1771            (AluType::Sub, Type::PtrToArray { id, offset: x }, Type::ScalarValue(data)) => {
1772                let offset = x.checked_sub(*data).ok_or_else(|| format!("XXX"))?;
1773                Type::PtrToArray { id: id.clone(), offset }
1774            }
1775            (
1776                AluType::Sub,
1777                Type::PtrToMemory { id: id1, offset: x1, .. },
1778                Type::PtrToMemory { id: id2, offset: x2, .. },
1779            )
1780            | (
1781                AluType::Sub,
1782                Type::PtrToStruct { id: id1, offset: x1, .. },
1783                Type::PtrToStruct { id: id2, offset: x2, .. },
1784            )
1785            | (
1786                AluType::Sub,
1787                Type::PtrToArray { id: id1, offset: x1 },
1788                Type::PtrToArray { id: id2, offset: x2 },
1789            ) if id1 == id2 => Type::from(op(*x1, *x2)),
1790            (AluType::Sub, Type::PtrToStack { offset: x1 }, Type::PtrToStack { offset: x2 }) => {
1791                Type::from(op(x1.reg(), x2.reg()))
1792            }
1793            (
1794                AluType::Sub,
1795                Type::PtrToArray { id: id1, .. },
1796                Type::PtrToEndArray { id: id2, .. },
1797            )
1798            | (
1799                AluType::Sub,
1800                Type::PtrToEndArray { id: id1, .. },
1801                Type::PtrToArray { id: id2, .. },
1802            ) if id1 == id2 => Type::UNKNOWN_SCALAR,
1803            _ => Type::default(),
1804        };
1805        Ok(result)
1806    }
1807
1808    fn alu(
1809        &mut self,
1810        op_name: Option<&str>,
1811        verification_context: &mut VerificationContext<'_>,
1812        dst: Register,
1813        src: Source,
1814        alu_type: AluType,
1815        op: impl Fn(ScalarValueData, ScalarValueData) -> ScalarValueData,
1816    ) -> Result<(), String> {
1817        if let Some(op_name) = op_name {
1818            bpf_log!(
1819                self,
1820                verification_context,
1821                "{op_name} {}, {}",
1822                display_register(dst),
1823                display_source(src)
1824            );
1825        }
1826        let op1 = self.reg(dst)?;
1827        let op2 = self.compute_source(src)?;
1828        let result = Self::apply_computation(self, op1, op2, alu_type, op)?;
1829        let mut next = self.next()?;
1830        next.set_reg(dst, result)?;
1831        verification_context.states.push(next);
1832        Ok(())
1833    }
1834
1835    fn log_atomic_operation(
1836        &mut self,
1837        op_name: &str,
1838        verification_context: &mut VerificationContext<'_>,
1839        fetch: bool,
1840        dst: Register,
1841        offset: i16,
1842        src: Register,
1843    ) {
1844        bpf_log!(
1845            self,
1846            verification_context,
1847            "lock {}{} [{}{}], {}",
1848            if fetch { "fetch " } else { "" },
1849            op_name,
1850            display_register(dst),
1851            print_offset(offset),
1852            display_register(src),
1853        );
1854    }
1855
1856    fn raw_atomic_operation(
1857        &mut self,
1858        op_name: &str,
1859        verification_context: &mut VerificationContext<'_>,
1860        width: DataWidth,
1861        fetch: bool,
1862        dst: Register,
1863        offset: i16,
1864        src: Register,
1865        op: impl FnOnce(&ComputationContext, Type, Type) -> Result<Type, String>,
1866    ) -> Result<(), String> {
1867        self.log_atomic_operation(op_name, verification_context, fetch, dst, offset, src);
1868        let addr = self.reg(dst)?;
1869        let value = self.reg(src)?;
1870        let field = Field::new(offset, width);
1871        let loaded_type = self.load_memory(verification_context, &addr, field)?;
1872        let result = op(self, loaded_type.clone(), value)?;
1873        let mut next = self.next()?;
1874        next.store_memory(verification_context, &addr, field, result)?;
1875        if fetch {
1876            next.set_reg(src, loaded_type)?;
1877        }
1878        verification_context.states.push(next);
1879        Ok(())
1880    }
1881
1882    fn atomic_operation(
1883        &mut self,
1884        op_name: &str,
1885        verification_context: &mut VerificationContext<'_>,
1886        width: DataWidth,
1887        fetch: bool,
1888        dst: Register,
1889        offset: i16,
1890        src: Register,
1891        alu_type: AluType,
1892        op: impl Fn(ScalarValueData, ScalarValueData) -> ScalarValueData,
1893    ) -> Result<(), String> {
1894        self.raw_atomic_operation(
1895            op_name,
1896            verification_context,
1897            width,
1898            fetch,
1899            dst,
1900            offset,
1901            src,
1902            |context: &ComputationContext, v1: Type, v2: Type| {
1903                Self::apply_computation(context, v1, v2, alu_type, op)
1904            },
1905        )
1906    }
1907
1908    fn raw_atomic_cmpxchg(
1909        &mut self,
1910        op_name: &str,
1911        verification_context: &mut VerificationContext<'_>,
1912        dst: Register,
1913        offset: i16,
1914        src: Register,
1915        jump_width: JumpWidth,
1916        op: impl Fn(ScalarValueData, ScalarValueData) -> Result<Option<bool>, ()>,
1917    ) -> Result<(), String> {
1918        self.log_atomic_operation(op_name, verification_context, true, dst, offset, src);
1919        let width = match jump_width {
1920            JumpWidth::W32 => DataWidth::U32,
1921            JumpWidth::W64 => DataWidth::U64,
1922        };
1923        let addr = self.reg(dst)?;
1924        let field = Field::new(offset, width);
1925        let dst = self.load_memory(verification_context, &addr, field)?;
1926        let value = self.reg(src)?;
1927        let r0 = self.reg(0)?;
1928        let branch = self.compute_branch(jump_width, &dst, &r0, op)?;
1929        // r0 = dst
1930        if branch.unwrap_or(true) {
1931            let mut next = self.next()?;
1932            let (dst, r0) =
1933                Type::constraint(&mut next, JumpType::Eq, jump_width, dst.clone(), r0.clone())?;
1934            next.set_reg(0, dst)?;
1935            next.store_memory(verification_context, &addr, field, value)?;
1936            verification_context.states.push(next);
1937        }
1938        // r0 != dst
1939        if !branch.unwrap_or(false) {
1940            let mut next = self.next()?;
1941            let (dst, r0) = Type::constraint(&mut next, JumpType::Ne, jump_width, dst, r0)?;
1942            next.set_reg(0, dst.clone())?;
1943            next.store_memory(verification_context, &addr, field, dst)?;
1944            verification_context.states.push(next);
1945        }
1946
1947        Ok(())
1948    }
1949    fn endianness<BO: ByteOrder>(
1950        &mut self,
1951        op_name: &str,
1952        verification_context: &mut VerificationContext<'_>,
1953        dst: Register,
1954        width: DataWidth,
1955    ) -> Result<(), String> {
1956        bpf_log!(self, verification_context, "{op_name}{} {}", width.bits(), display_register(dst),);
1957        let bit_op = |value: u64| match width {
1958            DataWidth::U16 => BO::read_u16((value as u16).as_bytes()) as u64,
1959            DataWidth::U32 => BO::read_u32((value as u32).as_bytes()) as u64,
1960            DataWidth::U64 => BO::read_u64(value.as_bytes()),
1961            _ => {
1962                panic!("Unexpected bit width for endianness operation");
1963            }
1964        };
1965        let value = self.reg(dst)?;
1966        let new_value = match value {
1967            Type::ScalarValue(data) => Type::ScalarValue(ScalarValueData::new(
1968                bit_op(data.value),
1969                bit_op(data.unknown_mask),
1970                bit_op(data.unwritten_mask),
1971                U64Range::max(),
1972            )),
1973            _ => Type::default(),
1974        };
1975        let mut next = self.next()?;
1976        next.set_reg(dst, new_value)?;
1977        verification_context.states.push(next);
1978        Ok(())
1979    }
1980
1981    fn compute_branch(
1982        &self,
1983        jump_width: JumpWidth,
1984        op1: &Type,
1985        op2: &Type,
1986        op: impl Fn(ScalarValueData, ScalarValueData) -> Result<Option<bool>, ()>,
1987    ) -> Result<Option<bool>, String> {
1988        match (jump_width, op1, op2) {
1989            (_, Type::ScalarValue(data1), Type::ScalarValue(data2)) => op(*data1, *data2),
1990            (JumpWidth::W64, Type::ScalarValue(data), Type::NullOr { .. })
1991            | (JumpWidth::W64, Type::NullOr { .. }, Type::ScalarValue(data))
1992                if data.is_zero() =>
1993            {
1994                Ok(None)
1995            }
1996
1997            (JumpWidth::W64, Type::ScalarValue(data), t) if data.is_zero() && t.is_non_zero() => {
1998                let non_zero =
1999                    ScalarValueData::UNKNOWN_WRITTEN.update_range(U64Range::new(1, u64::MAX));
2000                op(0.into(), non_zero)
2001            }
2002
2003            (JumpWidth::W64, t, Type::ScalarValue(data)) if data.is_zero() && t.is_non_zero() => {
2004                let non_zero =
2005                    ScalarValueData::UNKNOWN_WRITTEN.update_range(U64Range::new(1, u64::MAX));
2006                op(non_zero, 0.into())
2007            }
2008
2009            (JumpWidth::W64, Type::PtrToStack { offset: x }, Type::PtrToStack { offset: y }) => {
2010                op(x.reg(), y.reg())
2011            }
2012
2013            (
2014                JumpWidth::W64,
2015                Type::PtrToMemory { id: id1, offset: x, .. },
2016                Type::PtrToMemory { id: id2, offset: y, .. },
2017            )
2018            | (
2019                JumpWidth::W64,
2020                Type::PtrToStruct { id: id1, offset: x, .. },
2021                Type::PtrToStruct { id: id2, offset: y, .. },
2022            )
2023            | (
2024                JumpWidth::W64,
2025                Type::PtrToArray { id: id1, offset: x, .. },
2026                Type::PtrToArray { id: id2, offset: y, .. },
2027            ) if *id1 == *id2 => op(*x, *y),
2028
2029            (JumpWidth::W64, Type::PtrToArray { id: id1, .. }, Type::PtrToEndArray { id: id2 })
2030            | (JumpWidth::W64, Type::PtrToEndArray { id: id1 }, Type::PtrToArray { id: id2, .. })
2031                if *id1 == *id2 =>
2032            {
2033                Ok(None)
2034            }
2035
2036            _ => Err(()),
2037        }
2038        .map_err(|_| "non permitted comparison".to_string())
2039    }
2040
2041    fn conditional_jump(
2042        &mut self,
2043        op_name: &str,
2044        verification_context: &mut VerificationContext<'_>,
2045        dst: Register,
2046        src: Source,
2047        offset: i16,
2048        jump_type: JumpType,
2049        jump_width: JumpWidth,
2050        op: impl Fn(ScalarValueData, ScalarValueData) -> Result<Option<bool>, ()>,
2051    ) -> Result<(), String> {
2052        bpf_log!(
2053            self,
2054            verification_context,
2055            "{op_name} {}, {}, {}",
2056            display_register(dst),
2057            display_source(src),
2058            if offset == 0 { format!("0") } else { print_offset(offset) },
2059        );
2060        let op1 = self.reg(dst)?;
2061        let op2 = self.compute_source(src.clone())?;
2062        let apply_constraints_and_register = |mut next: Self,
2063                                              jump_type: JumpType|
2064         -> Result<Self, String> {
2065            if jump_type != JumpType::Unknown {
2066                let (new_op1, new_op2) =
2067                    Type::constraint(&mut next, jump_type, jump_width, op1.clone(), op2.clone())?;
2068                if dst < REGISTER_COUNT {
2069                    next.set_reg(dst, new_op1)?;
2070                }
2071                match src {
2072                    Source::Reg(r) => {
2073                        next.set_reg(r, new_op2)?;
2074                    }
2075                    _ => {
2076                        // Nothing to do
2077                    }
2078                }
2079            }
2080            Ok(next)
2081        };
2082        let branch = self.compute_branch(jump_width, &op1, &op2, op)?;
2083        let parent = Some(Arc::new(self.clone()));
2084        if branch.unwrap_or(true) {
2085            // Do the jump
2086            verification_context.states.push(apply_constraints_and_register(
2087                self.jump_with_offset(offset, parent.clone())?,
2088                jump_type,
2089            )?);
2090        }
2091        if !branch.unwrap_or(false) {
2092            // Skip the jump
2093            verification_context.states.push(apply_constraints_and_register(
2094                self.jump_with_offset(0, parent)?,
2095                jump_type.invert(),
2096            )?);
2097        }
2098        Ok(())
2099    }
2100
2101    /// Handles the termination of a `ComputationContext`, performing branch cutting optimization.
2102    ///
2103    /// This method is called when it has been proven that the current context terminates (e.g.,
2104    /// reaches an `exit` instruction).
2105    ///
2106    /// The following steps are performed:
2107    /// 1. **Dependency Calculation:** The data dependencies of the context are computed based on
2108    ///    the dependencies of its terminated children and the instruction at the current PC.
2109    /// 2. **Context Broadening:** The context's state is broadened by clearing registers and stack
2110    ///    slots that are *not* in the calculated data dependencies. This optimization assumes that
2111    ///    data not used by the terminated branch is irrelevant for future execution paths.
2112    /// 3. **Termination Registration:** The broadened context is added to the set of terminating
2113    ///    contexts if it is not less than any existing terminating context at the same PC.
2114    /// 4. **Parent Termination:** If all the children of the current context have terminated,
2115    ///    its parent context is recursively terminated.
2116    fn terminate(self, verification_context: &mut VerificationContext<'_>) -> Result<(), String> {
2117        let mut next = Some(self);
2118        // Because of the potential length of the parent chain, do not use recursion.
2119        while let Some(mut current) = next.take() {
2120            // Take the parent to process it at the end and not keep it in the terminating
2121            // contexts.
2122            let parent = current.parent.take();
2123
2124            // 1. Compute the dependencies of the context using the dependencies of its children
2125            //    and the actual operation.
2126            let mut dependencies = *current.dependencies.get_mut();
2127
2128            dependencies.visit(
2129                &mut DataDependenciesVisitorContext {
2130                    calling_context: &verification_context.calling_context,
2131                    computation_context: &current,
2132                },
2133                verification_context.code[current.pc],
2134            )?;
2135
2136            // 2. Clear the state depending on the dependencies states
2137            for register in 0..GENERAL_REGISTER_COUNT {
2138                if !dependencies.has_reg(register) {
2139                    current.set_reg(register, Default::default())?;
2140                }
2141            }
2142            if dependencies.stack == 0 {
2143                current.stack.data.clear();
2144            } else if dependencies.stack != !0 {
2145                current.stack.data.retain(|k, _| dependencies.has_stack(*k));
2146            }
2147
2148            // 3. Add the cleared state to the set of `terminating_contexts`
2149            let terminating_contexts = &mut verification_context.terminating_contexts[current.pc];
2150            let mut is_dominated = false;
2151            terminating_contexts.retain(|c| match c.computation_context.partial_cmp(&current) {
2152                Some(Ordering::Less) => false,
2153                Some(Ordering::Equal) | Some(Ordering::Greater) => {
2154                    // If the list contains a context greater or equal to the current one, it
2155                    // should not be added.
2156                    is_dominated = true;
2157                    true
2158                }
2159                _ => true,
2160            });
2161            if !is_dominated {
2162                terminating_contexts
2163                    .push(TerminatingContext { computation_context: current, dependencies });
2164            }
2165
2166            // 4. Register the computed dependencies in our parent, and terminate it if all
2167            //    dependencies has been computed.
2168            if let Some(parent) = parent {
2169                parent.dependencies.lock().merge(dependencies);
2170                // To check whether all dependencies have been computed, rely on the fact that the
2171                // Arc count of the parent keep track of how many dependencies are left.
2172                next = Arc::into_inner(parent);
2173            }
2174        }
2175        Ok(())
2176    }
2177}
2178
2179impl Drop for ComputationContext {
2180    fn drop(&mut self) {
2181        let mut next = self.parent.take().and_then(Arc::into_inner);
2182        // Because of the potential length of the parent chain, do not use recursion.
2183        while let Some(mut current) = next {
2184            next = current.parent.take().and_then(Arc::into_inner);
2185        }
2186    }
2187}
2188
2189/// Two types are ordered with `t1` > `t2` if a proof that a program in a state `t1` finish is also
2190/// a proof that a program in a state `t2` finish.
2191impl PartialOrd for ComputationContext {
2192    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2193        if self.pc != other.pc || self.resources.len() != other.resources.len() {
2194            return None;
2195        }
2196        let mut result = Type::compare_list(self.registers.iter(), other.registers.iter())?;
2197        result = associate_orderings(result, self.stack.partial_cmp(&other.stack)?)?;
2198        if self.resources != other.resources {
2199            return None;
2200        }
2201        let mut array_bound_iter1 = self.array_bounds.iter().peekable();
2202        let mut array_bound_iter2 = other.array_bounds.iter().peekable();
2203        let result = loop {
2204            match (array_bound_iter1.peek().cloned(), array_bound_iter2.peek().cloned()) {
2205                (None, None) => break result,
2206                (None, _) => break associate_orderings(result, Ordering::Greater)?,
2207                (_, None) => break associate_orderings(result, Ordering::Less)?,
2208                (Some((k1, v1)), Some((k2, v2))) => match k1.cmp(k2) {
2209                    Ordering::Equal => {
2210                        array_bound_iter1.next();
2211                        array_bound_iter2.next();
2212                        // The comparison is intentionally reversed (v2.cmp(v1) instead of
2213                        // v1.cmp(v2)). `v1` is the bound of the explored state (`self`) and `v2` is
2214                        // the bound of the current state (`other`). A smaller bound value
2215                        // represents a stronger safety guarantee (fewer assumed valid bytes,
2216                        // meaning a more restricted program). Therefore, a state with a smaller
2217                        // bound dominates (is "Greater" than) a state with a larger bound.
2218                        result = associate_orderings(result, v2.cmp(v1))?;
2219                    }
2220                    v @ Ordering::Less => {
2221                        array_bound_iter1.next();
2222                        result = associate_orderings(result, v)?;
2223                    }
2224                    v @ Ordering::Greater => {
2225                        array_bound_iter2.next();
2226                        result = associate_orderings(result, v)?;
2227                    }
2228                },
2229            }
2230        };
2231        Some(result)
2232    }
2233}
2234
2235/// Represents the read data dependencies of an eBPF program branch.
2236///
2237/// This struct tracks which registers and stack positions are *read* by the
2238/// instructions within a branch of the eBPF program.  This information is used
2239/// during branch cutting optimization to broaden terminating contexts.
2240///
2241/// The verifier assumes that data not read by a terminated branch is irrelevant
2242/// for future execution paths and can be safely cleared.
2243#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2244struct DataDependencies {
2245    /// A bitset of registers read by the children of a context.
2246    registers: u16,
2247    /// A bitset of stack positions read by the children of a context.
2248    stack: u64,
2249}
2250
2251impl DataDependencies {
2252    #[inline]
2253    fn has_reg(&self, reg: Register) -> bool {
2254        (self.registers & (1_u16 << reg)) != 0
2255    }
2256
2257    #[inline]
2258    fn add_reg(&mut self, reg: Register) {
2259        self.registers |= 1_u16 << reg;
2260    }
2261
2262    #[inline]
2263    fn remove_reg(&mut self, reg: Register) -> bool {
2264        let mask = 1_u16 << reg;
2265        let was_set = (self.registers & mask) != 0;
2266        self.registers &= !mask;
2267        was_set
2268    }
2269
2270    #[inline]
2271    fn has_stack(&self, slot: usize) -> bool {
2272        (self.stack & (1_u64 << slot)) != 0
2273    }
2274
2275    #[inline]
2276    fn add_stack(&mut self, slot: usize) {
2277        self.stack |= 1_u64 << slot;
2278    }
2279
2280    #[inline]
2281    fn remove_stack(&mut self, slot: usize) -> bool {
2282        let mask = 1_u64 << slot;
2283        let was_set = (self.stack & mask) != 0;
2284        self.stack &= !mask;
2285        was_set
2286    }
2287
2288    #[inline]
2289    fn add_stack_range(&mut self, start: usize, end: usize) {
2290        if end > start {
2291            let count = end - start;
2292            let mask = if count >= 64 { !0 } else { ((1_u64 << count) - 1) << start };
2293            self.stack |= mask;
2294        }
2295    }
2296
2297    fn merge(&mut self, other: Self) {
2298        self.registers |= other.registers;
2299        self.stack |= other.stack;
2300    }
2301
2302    fn alu(&mut self, dst: Register, src: Source) -> Result<(), String> {
2303        // Only do something if the dst is read, otherwise the computation doesn't matter.
2304        if self.has_reg(dst) {
2305            if let Source::Reg(src) = src {
2306                self.add_reg(src);
2307            }
2308        }
2309        Ok(())
2310    }
2311
2312    fn jmp(&mut self, dst: Register, src: Source) -> Result<(), String> {
2313        self.add_reg(dst);
2314        if let Source::Reg(src) = src {
2315            self.add_reg(src);
2316        }
2317        Ok(())
2318    }
2319
2320    fn atomic(
2321        &mut self,
2322        context: &ComputationContext,
2323        fetch: bool,
2324        dst: Register,
2325        offset: i16,
2326        src: Register,
2327        width: DataWidth,
2328        is_cmpxchg: bool,
2329    ) -> Result<(), String> {
2330        let mut is_read = false;
2331        if is_cmpxchg && self.has_reg(0) {
2332            is_read = true;
2333        }
2334        if fetch && self.has_reg(src) {
2335            is_read = true;
2336        }
2337        let addr = context.reg(dst)?;
2338        if let Type::PtrToStack { offset: stack_offset } = addr {
2339            let stack_offset = stack_offset.add(offset);
2340            if !stack_offset.is_valid_offset() {
2341                return Err(format!("Invalid stack offset at {}", context.pc));
2342            }
2343            let slot = stack_offset.array_index();
2344            if is_read || self.has_stack(slot) {
2345                is_read = true;
2346                self.add_stack(slot);
2347            }
2348        }
2349        if is_read {
2350            self.add_reg(0);
2351            self.add_reg(src);
2352        }
2353        self.add_reg(dst);
2354        Ok(())
2355    }
2356}
2357
2358struct DataDependenciesVisitorContext<'a> {
2359    calling_context: &'a CallingContext,
2360    computation_context: &'a ComputationContext,
2361}
2362
2363impl BpfVisitor for DataDependencies {
2364    type Context<'a> = DataDependenciesVisitorContext<'a>;
2365
2366    fn add<'a>(
2367        &mut self,
2368        _context: &mut Self::Context<'a>,
2369        dst: Register,
2370        src: Source,
2371    ) -> Result<(), String> {
2372        self.alu(dst, src)
2373    }
2374    fn add64<'a>(
2375        &mut self,
2376        _context: &mut Self::Context<'a>,
2377        dst: Register,
2378        src: Source,
2379    ) -> Result<(), String> {
2380        self.alu(dst, src)
2381    }
2382    fn and<'a>(
2383        &mut self,
2384        _context: &mut Self::Context<'a>,
2385        dst: Register,
2386        src: Source,
2387    ) -> Result<(), String> {
2388        self.alu(dst, src)
2389    }
2390    fn and64<'a>(
2391        &mut self,
2392        _context: &mut Self::Context<'a>,
2393        dst: Register,
2394        src: Source,
2395    ) -> Result<(), String> {
2396        self.alu(dst, src)
2397    }
2398    fn arsh<'a>(
2399        &mut self,
2400        _context: &mut Self::Context<'a>,
2401        dst: Register,
2402        src: Source,
2403    ) -> Result<(), String> {
2404        self.alu(dst, src)
2405    }
2406    fn arsh64<'a>(
2407        &mut self,
2408        _context: &mut Self::Context<'a>,
2409        dst: Register,
2410        src: Source,
2411    ) -> Result<(), String> {
2412        self.alu(dst, src)
2413    }
2414    fn div<'a>(
2415        &mut self,
2416        _context: &mut Self::Context<'a>,
2417        dst: Register,
2418        src: Source,
2419    ) -> Result<(), String> {
2420        self.alu(dst, src)
2421    }
2422    fn div64<'a>(
2423        &mut self,
2424        _context: &mut Self::Context<'a>,
2425        dst: Register,
2426        src: Source,
2427    ) -> Result<(), String> {
2428        self.alu(dst, src)
2429    }
2430    fn lsh<'a>(
2431        &mut self,
2432        _context: &mut Self::Context<'a>,
2433        dst: Register,
2434        src: Source,
2435    ) -> Result<(), String> {
2436        self.alu(dst, src)
2437    }
2438    fn lsh64<'a>(
2439        &mut self,
2440        _context: &mut Self::Context<'a>,
2441        dst: Register,
2442        src: Source,
2443    ) -> Result<(), String> {
2444        self.alu(dst, src)
2445    }
2446    fn r#mod<'a>(
2447        &mut self,
2448        _context: &mut Self::Context<'a>,
2449        dst: Register,
2450        src: Source,
2451    ) -> Result<(), String> {
2452        self.alu(dst, src)
2453    }
2454    fn mod64<'a>(
2455        &mut self,
2456        _context: &mut Self::Context<'a>,
2457        dst: Register,
2458        src: Source,
2459    ) -> Result<(), String> {
2460        self.alu(dst, src)
2461    }
2462    fn mul<'a>(
2463        &mut self,
2464        _context: &mut Self::Context<'a>,
2465        dst: Register,
2466        src: Source,
2467    ) -> Result<(), String> {
2468        self.alu(dst, src)
2469    }
2470    fn mul64<'a>(
2471        &mut self,
2472        _context: &mut Self::Context<'a>,
2473        dst: Register,
2474        src: Source,
2475    ) -> Result<(), String> {
2476        self.alu(dst, src)
2477    }
2478    fn or<'a>(
2479        &mut self,
2480        _context: &mut Self::Context<'a>,
2481        dst: Register,
2482        src: Source,
2483    ) -> Result<(), String> {
2484        self.alu(dst, src)
2485    }
2486    fn or64<'a>(
2487        &mut self,
2488        _context: &mut Self::Context<'a>,
2489        dst: Register,
2490        src: Source,
2491    ) -> Result<(), String> {
2492        self.alu(dst, src)
2493    }
2494    fn rsh<'a>(
2495        &mut self,
2496        _context: &mut Self::Context<'a>,
2497        dst: Register,
2498        src: Source,
2499    ) -> Result<(), String> {
2500        self.alu(dst, src)
2501    }
2502    fn rsh64<'a>(
2503        &mut self,
2504        _context: &mut Self::Context<'a>,
2505        dst: Register,
2506        src: Source,
2507    ) -> Result<(), String> {
2508        self.alu(dst, src)
2509    }
2510    fn sub<'a>(
2511        &mut self,
2512        _context: &mut Self::Context<'a>,
2513        dst: Register,
2514        src: Source,
2515    ) -> Result<(), String> {
2516        self.alu(dst, src)
2517    }
2518    fn sub64<'a>(
2519        &mut self,
2520        _context: &mut Self::Context<'a>,
2521        dst: Register,
2522        src: Source,
2523    ) -> Result<(), String> {
2524        self.alu(dst, src)
2525    }
2526    fn xor<'a>(
2527        &mut self,
2528        _context: &mut Self::Context<'a>,
2529        dst: Register,
2530        src: Source,
2531    ) -> Result<(), String> {
2532        self.alu(dst, src)
2533    }
2534    fn xor64<'a>(
2535        &mut self,
2536        _context: &mut Self::Context<'a>,
2537        dst: Register,
2538        src: Source,
2539    ) -> Result<(), String> {
2540        self.alu(dst, src)
2541    }
2542
2543    fn mov<'a>(
2544        &mut self,
2545        _context: &mut Self::Context<'a>,
2546        dst: Register,
2547        src: Source,
2548    ) -> Result<(), String> {
2549        if src == Source::Reg(dst) || !self.has_reg(dst) {
2550            return Ok(());
2551        }
2552        if let Source::Reg(src) = src {
2553            self.add_reg(src);
2554        }
2555        self.remove_reg(dst);
2556        Ok(())
2557    }
2558    fn mov64<'a>(
2559        &mut self,
2560        context: &mut Self::Context<'a>,
2561        dst: Register,
2562        src: Source,
2563    ) -> Result<(), String> {
2564        self.mov(context, dst, src)
2565    }
2566
2567    fn neg<'a>(&mut self, _context: &mut Self::Context<'a>, _dst: Register) -> Result<(), String> {
2568        // This is reading and writing the same value. This induces no change in dependencies.
2569        Ok(())
2570    }
2571    fn neg64<'a>(
2572        &mut self,
2573        _context: &mut Self::Context<'a>,
2574        _dst: Register,
2575    ) -> Result<(), String> {
2576        // This is reading and writing the same value. This induces no change in dependencies.
2577        Ok(())
2578    }
2579
2580    fn be<'a>(
2581        &mut self,
2582        _context: &mut Self::Context<'a>,
2583        _dst: Register,
2584        _width: DataWidth,
2585    ) -> Result<(), String> {
2586        // This is reading and writing the same value. This induces no change in dependencies.
2587        Ok(())
2588    }
2589    fn le<'a>(
2590        &mut self,
2591        _context: &mut Self::Context<'a>,
2592        _dst: Register,
2593        _width: DataWidth,
2594    ) -> Result<(), String> {
2595        // This is reading and writing the same value. This induces no change in dependencies.
2596        Ok(())
2597    }
2598
2599    fn call_external<'a>(
2600        &mut self,
2601        context: &mut Self::Context<'a>,
2602        index: u32,
2603    ) -> Result<(), String> {
2604        let Some(helper) = context.calling_context.helpers.get(&index).cloned() else {
2605            return Err(format!("unknown external function {}", index));
2606        };
2607        // A helper reads the stack region each `input` memory-parameter argument
2608        // points at, so those stack slots are data dependencies, like load/store.
2609        let comp = &context.computation_context;
2610        for (arg_index, arg) in helper.signature.args.iter().enumerate() {
2611            if let Type::MemoryParameter { size, input: true, .. } = arg {
2612                if let Type::PtrToStack { offset } = comp.reg((arg_index + 1) as Register)? {
2613                    let end = offset.add(size.size(comp)?);
2614                    if offset.is_valid_offset() && end.is_within_stack() {
2615                        let start_idx = offset.array_index();
2616                        let end_idx = end.array_index();
2617                        self.add_stack_range(start_idx, end_idx);
2618                        if end.sub_index() != 0 {
2619                            self.add_stack(end_idx);
2620                        }
2621                    }
2622                }
2623            }
2624        }
2625        // 0 is overwritten and 1 to 5 are scratch registers
2626        self.registers &= !0b0011_1111;
2627        // 1 to k are parameters.
2628        let num_args = helper.signature.args.len();
2629        self.registers |= ((1_u16 << num_args) - 1) << 1;
2630        Ok(())
2631    }
2632
2633    fn exit<'a>(&mut self, _context: &mut Self::Context<'a>) -> Result<(), String> {
2634        // This read r0 unconditionally.
2635        self.add_reg(0);
2636        Ok(())
2637    }
2638
2639    fn jump<'a>(&mut self, _context: &mut Self::Context<'a>, _offset: i16) -> Result<(), String> {
2640        // This doesn't do anything with values.
2641        Ok(())
2642    }
2643
2644    fn jeq<'a>(
2645        &mut self,
2646        _context: &mut Self::Context<'a>,
2647        dst: Register,
2648        src: Source,
2649        offset: i16,
2650    ) -> Result<(), String> {
2651        self.jmp(dst, src)
2652    }
2653    fn jeq64<'a>(
2654        &mut self,
2655        _context: &mut Self::Context<'a>,
2656        dst: Register,
2657        src: Source,
2658        offset: i16,
2659    ) -> Result<(), String> {
2660        self.jmp(dst, src)
2661    }
2662    fn jne<'a>(
2663        &mut self,
2664        _context: &mut Self::Context<'a>,
2665        dst: Register,
2666        src: Source,
2667        offset: i16,
2668    ) -> Result<(), String> {
2669        self.jmp(dst, src)
2670    }
2671    fn jne64<'a>(
2672        &mut self,
2673        _context: &mut Self::Context<'a>,
2674        dst: Register,
2675        src: Source,
2676        offset: i16,
2677    ) -> Result<(), String> {
2678        self.jmp(dst, src)
2679    }
2680    fn jge<'a>(
2681        &mut self,
2682        _context: &mut Self::Context<'a>,
2683        dst: Register,
2684        src: Source,
2685        offset: i16,
2686    ) -> Result<(), String> {
2687        self.jmp(dst, src)
2688    }
2689    fn jge64<'a>(
2690        &mut self,
2691        _context: &mut Self::Context<'a>,
2692        dst: Register,
2693        src: Source,
2694        offset: i16,
2695    ) -> Result<(), String> {
2696        self.jmp(dst, src)
2697    }
2698    fn jgt<'a>(
2699        &mut self,
2700        _context: &mut Self::Context<'a>,
2701        dst: Register,
2702        src: Source,
2703        offset: i16,
2704    ) -> Result<(), String> {
2705        self.jmp(dst, src)
2706    }
2707    fn jgt64<'a>(
2708        &mut self,
2709        _context: &mut Self::Context<'a>,
2710        dst: Register,
2711        src: Source,
2712        offset: i16,
2713    ) -> Result<(), String> {
2714        self.jmp(dst, src)
2715    }
2716    fn jle<'a>(
2717        &mut self,
2718        _context: &mut Self::Context<'a>,
2719        dst: Register,
2720        src: Source,
2721        offset: i16,
2722    ) -> Result<(), String> {
2723        self.jmp(dst, src)
2724    }
2725    fn jle64<'a>(
2726        &mut self,
2727        _context: &mut Self::Context<'a>,
2728        dst: Register,
2729        src: Source,
2730        offset: i16,
2731    ) -> Result<(), String> {
2732        self.jmp(dst, src)
2733    }
2734    fn jlt<'a>(
2735        &mut self,
2736        _context: &mut Self::Context<'a>,
2737        dst: Register,
2738        src: Source,
2739        offset: i16,
2740    ) -> Result<(), String> {
2741        self.jmp(dst, src)
2742    }
2743    fn jlt64<'a>(
2744        &mut self,
2745        _context: &mut Self::Context<'a>,
2746        dst: Register,
2747        src: Source,
2748        offset: i16,
2749    ) -> Result<(), String> {
2750        self.jmp(dst, src)
2751    }
2752    fn jsge<'a>(
2753        &mut self,
2754        _context: &mut Self::Context<'a>,
2755        dst: Register,
2756        src: Source,
2757        offset: i16,
2758    ) -> Result<(), String> {
2759        self.jmp(dst, src)
2760    }
2761    fn jsge64<'a>(
2762        &mut self,
2763        _context: &mut Self::Context<'a>,
2764        dst: Register,
2765        src: Source,
2766        offset: i16,
2767    ) -> Result<(), String> {
2768        self.jmp(dst, src)
2769    }
2770    fn jsgt<'a>(
2771        &mut self,
2772        _context: &mut Self::Context<'a>,
2773        dst: Register,
2774        src: Source,
2775        offset: i16,
2776    ) -> Result<(), String> {
2777        self.jmp(dst, src)
2778    }
2779    fn jsgt64<'a>(
2780        &mut self,
2781        _context: &mut Self::Context<'a>,
2782        dst: Register,
2783        src: Source,
2784        offset: i16,
2785    ) -> Result<(), String> {
2786        self.jmp(dst, src)
2787    }
2788    fn jsle<'a>(
2789        &mut self,
2790        _context: &mut Self::Context<'a>,
2791        dst: Register,
2792        src: Source,
2793        offset: i16,
2794    ) -> Result<(), String> {
2795        self.jmp(dst, src)
2796    }
2797    fn jsle64<'a>(
2798        &mut self,
2799        _context: &mut Self::Context<'a>,
2800        dst: Register,
2801        src: Source,
2802        offset: i16,
2803    ) -> Result<(), String> {
2804        self.jmp(dst, src)
2805    }
2806    fn jslt<'a>(
2807        &mut self,
2808        _context: &mut Self::Context<'a>,
2809        dst: Register,
2810        src: Source,
2811        offset: i16,
2812    ) -> Result<(), String> {
2813        self.jmp(dst, src)
2814    }
2815    fn jslt64<'a>(
2816        &mut self,
2817        _context: &mut Self::Context<'a>,
2818        dst: Register,
2819        src: Source,
2820        offset: i16,
2821    ) -> Result<(), String> {
2822        self.jmp(dst, src)
2823    }
2824    fn jset<'a>(
2825        &mut self,
2826        _context: &mut Self::Context<'a>,
2827        dst: Register,
2828        src: Source,
2829        offset: i16,
2830    ) -> Result<(), String> {
2831        self.jmp(dst, src)
2832    }
2833    fn jset64<'a>(
2834        &mut self,
2835        _context: &mut Self::Context<'a>,
2836        dst: Register,
2837        src: Source,
2838        offset: i16,
2839    ) -> Result<(), String> {
2840        self.jmp(dst, src)
2841    }
2842
2843    fn atomic_add<'a>(
2844        &mut self,
2845        context: &mut Self::Context<'a>,
2846        fetch: bool,
2847        dst: Register,
2848        offset: i16,
2849        src: Register,
2850    ) -> Result<(), String> {
2851        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U32, false)
2852    }
2853
2854    fn atomic_add64<'a>(
2855        &mut self,
2856        context: &mut Self::Context<'a>,
2857        fetch: bool,
2858        dst: Register,
2859        offset: i16,
2860        src: Register,
2861    ) -> Result<(), String> {
2862        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U64, false)
2863    }
2864
2865    fn atomic_and<'a>(
2866        &mut self,
2867        context: &mut Self::Context<'a>,
2868        fetch: bool,
2869        dst: Register,
2870        offset: i16,
2871        src: Register,
2872    ) -> Result<(), String> {
2873        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U32, false)
2874    }
2875
2876    fn atomic_and64<'a>(
2877        &mut self,
2878        context: &mut Self::Context<'a>,
2879        fetch: bool,
2880        dst: Register,
2881        offset: i16,
2882        src: Register,
2883    ) -> Result<(), String> {
2884        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U64, false)
2885    }
2886
2887    fn atomic_or<'a>(
2888        &mut self,
2889        context: &mut Self::Context<'a>,
2890        fetch: bool,
2891        dst: Register,
2892        offset: i16,
2893        src: Register,
2894    ) -> Result<(), String> {
2895        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U32, false)
2896    }
2897
2898    fn atomic_or64<'a>(
2899        &mut self,
2900        context: &mut Self::Context<'a>,
2901        fetch: bool,
2902        dst: Register,
2903        offset: i16,
2904        src: Register,
2905    ) -> Result<(), String> {
2906        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U64, false)
2907    }
2908
2909    fn atomic_xor<'a>(
2910        &mut self,
2911        context: &mut Self::Context<'a>,
2912        fetch: bool,
2913        dst: Register,
2914        offset: i16,
2915        src: Register,
2916    ) -> Result<(), String> {
2917        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U32, false)
2918    }
2919
2920    fn atomic_xor64<'a>(
2921        &mut self,
2922        context: &mut Self::Context<'a>,
2923        fetch: bool,
2924        dst: Register,
2925        offset: i16,
2926        src: Register,
2927    ) -> Result<(), String> {
2928        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U64, false)
2929    }
2930
2931    fn atomic_xchg<'a>(
2932        &mut self,
2933        context: &mut Self::Context<'a>,
2934        fetch: bool,
2935        dst: Register,
2936        offset: i16,
2937        src: Register,
2938    ) -> Result<(), String> {
2939        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U32, false)
2940    }
2941
2942    fn atomic_xchg64<'a>(
2943        &mut self,
2944        context: &mut Self::Context<'a>,
2945        fetch: bool,
2946        dst: Register,
2947        offset: i16,
2948        src: Register,
2949    ) -> Result<(), String> {
2950        self.atomic(&context.computation_context, fetch, dst, offset, src, DataWidth::U64, false)
2951    }
2952
2953    fn atomic_cmpxchg<'a>(
2954        &mut self,
2955        context: &mut Self::Context<'a>,
2956        dst: Register,
2957        offset: i16,
2958        src: Register,
2959    ) -> Result<(), String> {
2960        self.atomic(&context.computation_context, true, dst, offset, src, DataWidth::U32, true)
2961    }
2962
2963    fn atomic_cmpxchg64<'a>(
2964        &mut self,
2965        context: &mut Self::Context<'a>,
2966        dst: Register,
2967        offset: i16,
2968        src: Register,
2969    ) -> Result<(), String> {
2970        self.atomic(&context.computation_context, true, dst, offset, src, DataWidth::U64, true)
2971    }
2972
2973    fn load<'a>(
2974        &mut self,
2975        context: &mut Self::Context<'a>,
2976        dst: Register,
2977        offset: i16,
2978        src: Register,
2979        width: DataWidth,
2980    ) -> Result<(), String> {
2981        let context = &context.computation_context;
2982        if self.has_reg(dst) {
2983            let addr = context.reg(src)?;
2984            if let Type::PtrToStack { offset: stack_offset } = addr {
2985                let stack_offset = stack_offset.add(offset);
2986                if !stack_offset.is_valid_offset() {
2987                    return Err(format!("Invalid stack offset at {}", context.pc));
2988                }
2989                self.add_stack(stack_offset.array_index());
2990            }
2991        }
2992        self.add_reg(src);
2993        Ok(())
2994    }
2995
2996    fn load64<'a>(
2997        &mut self,
2998        _context: &mut Self::Context<'a>,
2999        dst: Register,
3000        _src: u8,
3001        _lower: u32,
3002    ) -> Result<(), String> {
3003        self.remove_reg(dst);
3004        Ok(())
3005    }
3006
3007    fn load_from_packet<'a>(
3008        &mut self,
3009        _context: &mut Self::Context<'a>,
3010        dst: Register,
3011        src: Register,
3012        _offset: i32,
3013        register_offset: Option<Register>,
3014        _width: DataWidth,
3015    ) -> Result<(), String> {
3016        // 1 to 5 are scratch registers (bits 1..=5)
3017        self.registers &= !0b0011_1110;
3018        // Only do something if the dst is read, otherwise the computation doesn't matter.
3019        if self.remove_reg(dst) {
3020            self.add_reg(src);
3021            if let Some(reg) = register_offset {
3022                self.add_reg(reg);
3023            }
3024        }
3025        Ok(())
3026    }
3027
3028    fn store<'a>(
3029        &mut self,
3030        context: &mut Self::Context<'a>,
3031        dst: Register,
3032        offset: i16,
3033        src: Source,
3034        width: DataWidth,
3035    ) -> Result<(), String> {
3036        let context = &context.computation_context;
3037        let addr = context.reg(dst)?;
3038        if let Type::PtrToStack { offset: stack_offset } = addr {
3039            let stack_offset = stack_offset.add(offset);
3040            if !stack_offset.is_valid_offset() {
3041                return Err(format!("Invalid stack offset at {}", context.pc));
3042            }
3043            if self.remove_stack(stack_offset.array_index()) {
3044                if let Source::Reg(src) = src {
3045                    self.add_reg(src);
3046                }
3047            }
3048        } else {
3049            if let Source::Reg(src) = src {
3050                self.add_reg(src);
3051            }
3052            self.add_reg(dst);
3053        }
3054
3055        Ok(())
3056    }
3057}
3058
3059#[derive(Debug)]
3060struct TerminatingContext {
3061    computation_context: ComputationContext,
3062    dependencies: DataDependencies,
3063}
3064
3065#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3066enum AluType {
3067    Plain,
3068    Sub,
3069    Add,
3070}
3071
3072impl AluType {
3073    /// Can this operation be done one a pointer and a scalar.
3074    fn is_ptr_compatible(&self) -> bool {
3075        match self {
3076            Self::Sub | Self::Add => true,
3077            _ => false,
3078        }
3079    }
3080}
3081
3082#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3083enum JumpWidth {
3084    W32,
3085    W64,
3086}
3087
3088#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3089enum JumpType {
3090    Eq,
3091    Ge,
3092    Gt,
3093    Le,
3094    LooseComparaison,
3095    Lt,
3096    Ne,
3097    StrictComparaison,
3098    Unknown,
3099}
3100
3101impl JumpType {
3102    fn invert(&self) -> Self {
3103        match self {
3104            Self::Eq => Self::Ne,
3105            Self::Ge => Self::Lt,
3106            Self::Gt => Self::Le,
3107            Self::Le => Self::Gt,
3108            Self::LooseComparaison => Self::StrictComparaison,
3109            Self::Lt => Self::Ge,
3110            Self::Ne => Self::Eq,
3111            Self::StrictComparaison => Self::LooseComparaison,
3112            Self::Unknown => Self::Unknown,
3113        }
3114    }
3115
3116    fn is_strict(&self) -> bool {
3117        match self {
3118            Self::Gt | Self::Lt | Self::Ne | Self::StrictComparaison => true,
3119            _ => false,
3120        }
3121    }
3122}
3123
3124fn display_register(register: Register) -> String {
3125    format!("%r{register}")
3126}
3127
3128fn display_source(src: Source) -> String {
3129    match src {
3130        Source::Reg(r) => display_register(r),
3131        Source::Value(v) => format!("0x{v:x}"),
3132    }
3133}
3134
3135impl BpfVisitor for ComputationContext {
3136    type Context<'a> = VerificationContext<'a>;
3137
3138    fn add<'a>(
3139        &mut self,
3140        context: &mut Self::Context<'a>,
3141        dst: Register,
3142        src: Source,
3143    ) -> Result<(), String> {
3144        self.alu(Some("add32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x + y))
3145    }
3146    fn add64<'a>(
3147        &mut self,
3148        context: &mut Self::Context<'a>,
3149        dst: Register,
3150        src: Source,
3151    ) -> Result<(), String> {
3152        self.alu(Some("add"), context, dst, src, AluType::Add, |x, y| x + y)
3153    }
3154    fn and<'a>(
3155        &mut self,
3156        context: &mut Self::Context<'a>,
3157        dst: Register,
3158        src: Source,
3159    ) -> Result<(), String> {
3160        self.alu(Some("and32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x & y))
3161    }
3162    fn and64<'a>(
3163        &mut self,
3164        context: &mut Self::Context<'a>,
3165        dst: Register,
3166        src: Source,
3167    ) -> Result<(), String> {
3168        self.alu(Some("and"), context, dst, src, AluType::Plain, |x, y| x & y)
3169    }
3170    fn arsh<'a>(
3171        &mut self,
3172        context: &mut Self::Context<'a>,
3173        dst: Register,
3174        src: Source,
3175    ) -> Result<(), String> {
3176        self.alu(Some("arsh32"), context, dst, src, AluType::Plain, |x, y| {
3177            alu32(x, y, |x, y| x.ashr(y))
3178        })
3179    }
3180    fn arsh64<'a>(
3181        &mut self,
3182        context: &mut Self::Context<'a>,
3183        dst: Register,
3184        src: Source,
3185    ) -> Result<(), String> {
3186        self.alu(Some("arsh"), context, dst, src, AluType::Plain, |x, y| x.ashr(y))
3187    }
3188    fn div<'a>(
3189        &mut self,
3190        context: &mut Self::Context<'a>,
3191        dst: Register,
3192        src: Source,
3193    ) -> Result<(), String> {
3194        self.alu(Some("div32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x / y))
3195    }
3196    fn div64<'a>(
3197        &mut self,
3198        context: &mut Self::Context<'a>,
3199        dst: Register,
3200        src: Source,
3201    ) -> Result<(), String> {
3202        self.alu(Some("div"), context, dst, src, AluType::Plain, |x, y| x / y)
3203    }
3204    fn lsh<'a>(
3205        &mut self,
3206        context: &mut Self::Context<'a>,
3207        dst: Register,
3208        src: Source,
3209    ) -> Result<(), String> {
3210        self.alu(Some("lsh32"), context, dst, src, AluType::Plain, |x, y| {
3211            alu32(x, y, |x, y| x << y)
3212        })
3213    }
3214    fn lsh64<'a>(
3215        &mut self,
3216        context: &mut Self::Context<'a>,
3217        dst: Register,
3218        src: Source,
3219    ) -> Result<(), String> {
3220        self.alu(Some("lsh"), context, dst, src, AluType::Plain, |x, y| x << y)
3221    }
3222    fn r#mod<'a>(
3223        &mut self,
3224        context: &mut Self::Context<'a>,
3225        dst: Register,
3226        src: Source,
3227    ) -> Result<(), String> {
3228        self.alu(Some("mod32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x % y))
3229    }
3230    fn mod64<'a>(
3231        &mut self,
3232        context: &mut Self::Context<'a>,
3233        dst: Register,
3234        src: Source,
3235    ) -> Result<(), String> {
3236        self.alu(Some("mod"), context, dst, src, AluType::Plain, |x, y| x % y)
3237    }
3238    fn mov<'a>(
3239        &mut self,
3240        context: &mut Self::Context<'a>,
3241        dst: Register,
3242        src: Source,
3243    ) -> Result<(), String> {
3244        bpf_log!(self, context, "mov32 {}, {}", display_register(dst), display_source(src));
3245        let src = self.compute_source(src)?;
3246        let value = match src {
3247            Type::ScalarValue(data) => {
3248                let value = (data.value as u32) as u64;
3249                let unknown_mask = (data.unknown_mask as u32) as u64;
3250                let unwritten_mask = (data.unwritten_mask as u32) as u64;
3251                let urange = U64Range::compute_range_for_bytes_swap(0.into(), data.urange, 0, 0, 4);
3252                Type::ScalarValue(ScalarValueData::new(value, unknown_mask, unwritten_mask, urange))
3253            }
3254            _ => Type::default(),
3255        };
3256        let mut next = self.next()?;
3257        next.set_reg(dst, value)?;
3258        context.states.push(next);
3259        Ok(())
3260    }
3261    fn mov64<'a>(
3262        &mut self,
3263        context: &mut Self::Context<'a>,
3264        dst: Register,
3265        src: Source,
3266    ) -> Result<(), String> {
3267        bpf_log!(self, context, "mov {}, {}", display_register(dst), display_source(src));
3268        let src = self.compute_source(src)?;
3269        let mut next = self.next()?;
3270        next.set_reg(dst, src)?;
3271        context.states.push(next);
3272        Ok(())
3273    }
3274    fn mul<'a>(
3275        &mut self,
3276        context: &mut Self::Context<'a>,
3277        dst: Register,
3278        src: Source,
3279    ) -> Result<(), String> {
3280        self.alu(Some("mul32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x * y))
3281    }
3282    fn mul64<'a>(
3283        &mut self,
3284        context: &mut Self::Context<'a>,
3285        dst: Register,
3286        src: Source,
3287    ) -> Result<(), String> {
3288        self.alu(Some("mul"), context, dst, src, AluType::Plain, |x, y| x * y)
3289    }
3290    fn or<'a>(
3291        &mut self,
3292        context: &mut Self::Context<'a>,
3293        dst: Register,
3294        src: Source,
3295    ) -> Result<(), String> {
3296        self.alu(Some("or32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x | y))
3297    }
3298    fn or64<'a>(
3299        &mut self,
3300        context: &mut Self::Context<'a>,
3301        dst: Register,
3302        src: Source,
3303    ) -> Result<(), String> {
3304        self.alu(Some("or"), context, dst, src, AluType::Plain, |x, y| x | y)
3305    }
3306    fn rsh<'a>(
3307        &mut self,
3308        context: &mut Self::Context<'a>,
3309        dst: Register,
3310        src: Source,
3311    ) -> Result<(), String> {
3312        self.alu(Some("rsh32"), context, dst, src, AluType::Plain, |x, y| {
3313            alu32(x, y, |x, y| x >> y)
3314        })
3315    }
3316    fn rsh64<'a>(
3317        &mut self,
3318        context: &mut Self::Context<'a>,
3319        dst: Register,
3320        src: Source,
3321    ) -> Result<(), String> {
3322        self.alu(Some("rsh"), context, dst, src, AluType::Plain, |x, y| x >> y)
3323    }
3324    fn sub<'a>(
3325        &mut self,
3326        context: &mut Self::Context<'a>,
3327        dst: Register,
3328        src: Source,
3329    ) -> Result<(), String> {
3330        self.alu(Some("sub32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x - y))
3331    }
3332    fn sub64<'a>(
3333        &mut self,
3334        context: &mut Self::Context<'a>,
3335        dst: Register,
3336        src: Source,
3337    ) -> Result<(), String> {
3338        self.alu(Some("sub"), context, dst, src, AluType::Sub, |x, y| x - y)
3339    }
3340    fn xor<'a>(
3341        &mut self,
3342        context: &mut Self::Context<'a>,
3343        dst: Register,
3344        src: Source,
3345    ) -> Result<(), String> {
3346        self.alu(Some("xor32"), context, dst, src, AluType::Plain, |x, y| alu32(x, y, |x, y| x ^ y))
3347    }
3348    fn xor64<'a>(
3349        &mut self,
3350        context: &mut Self::Context<'a>,
3351        dst: Register,
3352        src: Source,
3353    ) -> Result<(), String> {
3354        self.alu(Some("xor"), context, dst, src, AluType::Plain, |x, y| x ^ y)
3355    }
3356
3357    fn neg<'a>(&mut self, context: &mut Self::Context<'a>, dst: Register) -> Result<(), String> {
3358        bpf_log!(self, context, "neg32 {}", display_register(dst));
3359        self.alu(None, context, dst, Source::Value(0), AluType::Plain, |x, y| {
3360            alu32(x, y, |x, _y| -x)
3361        })
3362    }
3363    fn neg64<'a>(&mut self, context: &mut Self::Context<'a>, dst: Register) -> Result<(), String> {
3364        bpf_log!(self, context, "neg {}", display_register(dst));
3365        self.alu(None, context, dst, Source::Value(0), AluType::Plain, |x, _y| -x)
3366    }
3367
3368    fn be<'a>(
3369        &mut self,
3370        context: &mut Self::Context<'a>,
3371        dst: Register,
3372        width: DataWidth,
3373    ) -> Result<(), String> {
3374        self.endianness::<BigEndian>("be", context, dst, width)
3375    }
3376
3377    fn le<'a>(
3378        &mut self,
3379        context: &mut Self::Context<'a>,
3380        dst: Register,
3381        width: DataWidth,
3382    ) -> Result<(), String> {
3383        self.endianness::<LittleEndian>("le", context, dst, width)
3384    }
3385
3386    fn call_external<'a>(
3387        &mut self,
3388        context: &mut Self::Context<'a>,
3389        index: u32,
3390    ) -> Result<(), String> {
3391        bpf_log!(self, context, "call 0x{:x}", index);
3392        let Some(helper) = context.calling_context.helpers.get(&index).cloned() else {
3393            return Err(format!("unknown external function {}", index));
3394        };
3395        let HelperDefinition { signature, name, .. } = helper;
3396        debug_assert!(signature.args.len() <= 5);
3397        let mut next = self.next()?;
3398        for (arg_index, arg) in signature.args.iter().enumerate() {
3399            let reg = (arg_index + 1) as u8;
3400            self.reg(reg)?.match_parameter_type(context, self, name, arg, arg_index, &mut next)?
3401        }
3402        // Parameters have been validated, specify the return value on return.
3403        if signature.invalidate_array_bounds {
3404            next.array_bounds.clear();
3405        }
3406        let value =
3407            self.resolve_return_value(context, &signature.return_value, &mut next, false)?;
3408        next.set_reg(0, value)?;
3409        for i in 1..=5 {
3410            next.set_reg(i, Type::default())?;
3411        }
3412        context.states.push(next);
3413        Ok(())
3414    }
3415
3416    fn exit<'a>(&mut self, context: &mut Self::Context<'a>) -> Result<(), String> {
3417        bpf_log!(self, context, "exit");
3418        if !self.reg(0)?.is_written_scalar() {
3419            return Err("register 0 is incorrect at exit time".to_string());
3420        }
3421        if !self.resources.is_empty() {
3422            return Err("some resources have not been released at exit time".to_string());
3423        }
3424        self.terminated = true;
3425        Ok(())
3426    }
3427
3428    fn jump<'a>(&mut self, context: &mut Self::Context<'a>, offset: i16) -> Result<(), String> {
3429        bpf_log!(self, context, "ja {}", offset);
3430        let parent = Some(Arc::new(self.clone()));
3431        context.states.push(self.jump_with_offset(offset, parent)?);
3432        Ok(())
3433    }
3434
3435    fn jeq<'a>(
3436        &mut self,
3437        context: &mut Self::Context<'a>,
3438        dst: Register,
3439        src: Source,
3440        offset: i16,
3441    ) -> Result<(), String> {
3442        self.conditional_jump(
3443            "jeq32",
3444            context,
3445            dst,
3446            src,
3447            offset,
3448            JumpType::Eq,
3449            JumpWidth::W32,
3450            |x, y| {
3451                comp32(x, y, |x, y| {
3452                    // x == y
3453                    if x.min == x.max && x.min == y.min && x.min == y.max {
3454                        return Some(true);
3455                    }
3456                    if x.max < y.min || y.max < x.min {
3457                        return Some(false);
3458                    }
3459                    None
3460                })
3461            },
3462        )
3463    }
3464    fn jeq64<'a>(
3465        &mut self,
3466        context: &mut Self::Context<'a>,
3467        dst: Register,
3468        src: Source,
3469        offset: i16,
3470    ) -> Result<(), String> {
3471        self.conditional_jump(
3472            "jeq",
3473            context,
3474            dst,
3475            src,
3476            offset,
3477            JumpType::Eq,
3478            JumpWidth::W64,
3479            |x, y| {
3480                comp64(x, y, |x, y| {
3481                    // x == y
3482                    if x.min == x.max && x.min == y.min && x.min == y.max {
3483                        return Some(true);
3484                    }
3485                    if x.max < y.min || y.max < x.min {
3486                        return Some(false);
3487                    }
3488                    None
3489                })
3490            },
3491        )
3492    }
3493    fn jne<'a>(
3494        &mut self,
3495        context: &mut Self::Context<'a>,
3496        dst: Register,
3497        src: Source,
3498        offset: i16,
3499    ) -> Result<(), String> {
3500        self.conditional_jump(
3501            "jne32",
3502            context,
3503            dst,
3504            src,
3505            offset,
3506            JumpType::Ne,
3507            JumpWidth::W32,
3508            |x, y| {
3509                comp32(x, y, |x, y| {
3510                    // x != y
3511                    if x.min == x.max && x.min == y.min && x.min == y.max {
3512                        return Some(false);
3513                    }
3514                    if x.max < y.min || y.max < x.min {
3515                        return Some(true);
3516                    }
3517                    None
3518                })
3519            },
3520        )
3521    }
3522    fn jne64<'a>(
3523        &mut self,
3524        context: &mut Self::Context<'a>,
3525        dst: Register,
3526        src: Source,
3527        offset: i16,
3528    ) -> Result<(), String> {
3529        self.conditional_jump(
3530            "jne",
3531            context,
3532            dst,
3533            src,
3534            offset,
3535            JumpType::Ne,
3536            JumpWidth::W64,
3537            |x, y| {
3538                comp64(x, y, |x, y| {
3539                    // x != y
3540                    if x.min == x.max && x.min == y.min && x.min == y.max {
3541                        return Some(false);
3542                    }
3543                    if x.max < y.min || y.max < x.min {
3544                        return Some(true);
3545                    }
3546                    None
3547                })
3548            },
3549        )
3550    }
3551    fn jge<'a>(
3552        &mut self,
3553        context: &mut Self::Context<'a>,
3554        dst: Register,
3555        src: Source,
3556        offset: i16,
3557    ) -> Result<(), String> {
3558        self.conditional_jump(
3559            "jge32",
3560            context,
3561            dst,
3562            src,
3563            offset,
3564            JumpType::Ge,
3565            JumpWidth::W32,
3566            |x, y| {
3567                comp32(x, y, |x, y| {
3568                    // x >= y
3569                    if x.min >= y.max {
3570                        return Some(true);
3571                    }
3572                    if y.min > x.max {
3573                        return Some(false);
3574                    }
3575                    None
3576                })
3577            },
3578        )
3579    }
3580    fn jge64<'a>(
3581        &mut self,
3582        context: &mut Self::Context<'a>,
3583        dst: Register,
3584        src: Source,
3585        offset: i16,
3586    ) -> Result<(), String> {
3587        self.conditional_jump(
3588            "jge",
3589            context,
3590            dst,
3591            src,
3592            offset,
3593            JumpType::Ge,
3594            JumpWidth::W64,
3595            |x, y| {
3596                comp64(x, y, |x, y| {
3597                    // x >= y
3598                    if x.min >= y.max {
3599                        return Some(true);
3600                    }
3601                    if y.min > x.max {
3602                        return Some(false);
3603                    }
3604                    None
3605                })
3606            },
3607        )
3608    }
3609    fn jgt<'a>(
3610        &mut self,
3611        context: &mut Self::Context<'a>,
3612        dst: Register,
3613        src: Source,
3614        offset: i16,
3615    ) -> Result<(), String> {
3616        self.conditional_jump(
3617            "jgt32",
3618            context,
3619            dst,
3620            src,
3621            offset,
3622            JumpType::Gt,
3623            JumpWidth::W32,
3624            |x, y| {
3625                comp32(x, y, |x, y| {
3626                    // x > y
3627                    if x.min > y.max {
3628                        return Some(true);
3629                    }
3630                    if y.min >= x.max {
3631                        return Some(false);
3632                    }
3633                    None
3634                })
3635            },
3636        )
3637    }
3638    fn jgt64<'a>(
3639        &mut self,
3640        context: &mut Self::Context<'a>,
3641        dst: Register,
3642        src: Source,
3643        offset: i16,
3644    ) -> Result<(), String> {
3645        self.conditional_jump(
3646            "jgt",
3647            context,
3648            dst,
3649            src,
3650            offset,
3651            JumpType::Gt,
3652            JumpWidth::W64,
3653            |x, y| {
3654                comp64(x, y, |x, y| {
3655                    // x > y
3656                    if x.min > y.max {
3657                        return Some(true);
3658                    }
3659                    if y.min >= x.max {
3660                        return Some(false);
3661                    }
3662                    None
3663                })
3664            },
3665        )
3666    }
3667    fn jle<'a>(
3668        &mut self,
3669        context: &mut Self::Context<'a>,
3670        dst: Register,
3671        src: Source,
3672        offset: i16,
3673    ) -> Result<(), String> {
3674        self.conditional_jump(
3675            "jle32",
3676            context,
3677            dst,
3678            src,
3679            offset,
3680            JumpType::Le,
3681            JumpWidth::W32,
3682            |x, y| {
3683                comp32(x, y, |x, y| {
3684                    // x <= y
3685                    if x.max <= y.min {
3686                        return Some(true);
3687                    }
3688                    if y.max < x.min {
3689                        return Some(false);
3690                    }
3691                    None
3692                })
3693            },
3694        )
3695    }
3696    fn jle64<'a>(
3697        &mut self,
3698        context: &mut Self::Context<'a>,
3699        dst: Register,
3700        src: Source,
3701        offset: i16,
3702    ) -> Result<(), String> {
3703        self.conditional_jump(
3704            "jle",
3705            context,
3706            dst,
3707            src,
3708            offset,
3709            JumpType::Le,
3710            JumpWidth::W64,
3711            |x, y| {
3712                comp64(x, y, |x, y| {
3713                    // x <= y
3714                    if x.max <= y.min {
3715                        return Some(true);
3716                    }
3717                    if y.max < x.min {
3718                        return Some(false);
3719                    }
3720                    None
3721                })
3722            },
3723        )
3724    }
3725    fn jlt<'a>(
3726        &mut self,
3727        context: &mut Self::Context<'a>,
3728        dst: Register,
3729        src: Source,
3730        offset: i16,
3731    ) -> Result<(), String> {
3732        self.conditional_jump(
3733            "jlt32",
3734            context,
3735            dst,
3736            src,
3737            offset,
3738            JumpType::Lt,
3739            JumpWidth::W32,
3740            |x, y| {
3741                comp32(x, y, |x, y| {
3742                    // x < y
3743                    if x.max < y.min {
3744                        return Some(true);
3745                    }
3746                    if y.max <= x.min {
3747                        return Some(false);
3748                    }
3749                    None
3750                })
3751            },
3752        )
3753    }
3754    fn jlt64<'a>(
3755        &mut self,
3756        context: &mut Self::Context<'a>,
3757        dst: Register,
3758        src: Source,
3759        offset: i16,
3760    ) -> Result<(), String> {
3761        self.conditional_jump(
3762            "jlt",
3763            context,
3764            dst,
3765            src,
3766            offset,
3767            JumpType::Lt,
3768            JumpWidth::W64,
3769            |x, y| {
3770                comp64(x, y, |x, y| {
3771                    // x < y
3772                    if x.max < y.min {
3773                        return Some(true);
3774                    }
3775                    if y.max <= x.min {
3776                        return Some(false);
3777                    }
3778                    None
3779                })
3780            },
3781        )
3782    }
3783    fn jsge<'a>(
3784        &mut self,
3785        context: &mut Self::Context<'a>,
3786        dst: Register,
3787        src: Source,
3788        offset: i16,
3789    ) -> Result<(), String> {
3790        self.conditional_jump(
3791            "jsge32",
3792            context,
3793            dst,
3794            src,
3795            offset,
3796            JumpType::LooseComparaison,
3797            JumpWidth::W32,
3798            |x, y| scomp32(x, y, |x, y| x >= y),
3799        )
3800    }
3801    fn jsge64<'a>(
3802        &mut self,
3803        context: &mut Self::Context<'a>,
3804        dst: Register,
3805        src: Source,
3806        offset: i16,
3807    ) -> Result<(), String> {
3808        self.conditional_jump(
3809            "jsge",
3810            context,
3811            dst,
3812            src,
3813            offset,
3814            JumpType::LooseComparaison,
3815            JumpWidth::W64,
3816            |x, y| scomp64(x, y, |x, y| x >= y),
3817        )
3818    }
3819    fn jsgt<'a>(
3820        &mut self,
3821        context: &mut Self::Context<'a>,
3822        dst: Register,
3823        src: Source,
3824        offset: i16,
3825    ) -> Result<(), String> {
3826        self.conditional_jump(
3827            "jsgt32",
3828            context,
3829            dst,
3830            src,
3831            offset,
3832            JumpType::StrictComparaison,
3833            JumpWidth::W32,
3834            |x, y| scomp32(x, y, |x, y| x > y),
3835        )
3836    }
3837    fn jsgt64<'a>(
3838        &mut self,
3839        context: &mut Self::Context<'a>,
3840        dst: Register,
3841        src: Source,
3842        offset: i16,
3843    ) -> Result<(), String> {
3844        self.conditional_jump(
3845            "jsgt",
3846            context,
3847            dst,
3848            src,
3849            offset,
3850            JumpType::StrictComparaison,
3851            JumpWidth::W64,
3852            |x, y| scomp64(x, y, |x, y| x > y),
3853        )
3854    }
3855    fn jsle<'a>(
3856        &mut self,
3857        context: &mut Self::Context<'a>,
3858        dst: Register,
3859        src: Source,
3860        offset: i16,
3861    ) -> Result<(), String> {
3862        self.conditional_jump(
3863            "jsle32",
3864            context,
3865            dst,
3866            src,
3867            offset,
3868            JumpType::LooseComparaison,
3869            JumpWidth::W32,
3870            |x, y| scomp32(x, y, |x, y| x <= y),
3871        )
3872    }
3873    fn jsle64<'a>(
3874        &mut self,
3875        context: &mut Self::Context<'a>,
3876        dst: Register,
3877        src: Source,
3878        offset: i16,
3879    ) -> Result<(), String> {
3880        self.conditional_jump(
3881            "jsle",
3882            context,
3883            dst,
3884            src,
3885            offset,
3886            JumpType::LooseComparaison,
3887            JumpWidth::W64,
3888            |x, y| scomp64(x, y, |x, y| x <= y),
3889        )
3890    }
3891    fn jslt<'a>(
3892        &mut self,
3893        context: &mut Self::Context<'a>,
3894        dst: Register,
3895        src: Source,
3896        offset: i16,
3897    ) -> Result<(), String> {
3898        self.conditional_jump(
3899            "jslt32",
3900            context,
3901            dst,
3902            src,
3903            offset,
3904            JumpType::StrictComparaison,
3905            JumpWidth::W32,
3906            |x, y| scomp32(x, y, |x, y| x < y),
3907        )
3908    }
3909    fn jslt64<'a>(
3910        &mut self,
3911        context: &mut Self::Context<'a>,
3912        dst: Register,
3913        src: Source,
3914        offset: i16,
3915    ) -> Result<(), String> {
3916        self.conditional_jump(
3917            "jslt",
3918            context,
3919            dst,
3920            src,
3921            offset,
3922            JumpType::StrictComparaison,
3923            JumpWidth::W64,
3924            |x, y| scomp64(x, y, |x, y| x < y),
3925        )
3926    }
3927    fn jset<'a>(
3928        &mut self,
3929        context: &mut Self::Context<'a>,
3930        dst: Register,
3931        src: Source,
3932        offset: i16,
3933    ) -> Result<(), String> {
3934        self.conditional_jump(
3935            "jset32",
3936            context,
3937            dst,
3938            src,
3939            offset,
3940            JumpType::Unknown,
3941            JumpWidth::W32,
3942            |x, y| {
3943                comp32(x, y, |x, y| {
3944                    // x & y != 0
3945                    if x.min != x.max || y.min != y.max {
3946                        return None;
3947                    }
3948                    Some(x.min & y.min != 0)
3949                })
3950            },
3951        )
3952    }
3953    fn jset64<'a>(
3954        &mut self,
3955        context: &mut Self::Context<'a>,
3956        dst: Register,
3957        src: Source,
3958        offset: i16,
3959    ) -> Result<(), String> {
3960        self.conditional_jump(
3961            "jset",
3962            context,
3963            dst,
3964            src,
3965            offset,
3966            JumpType::Unknown,
3967            JumpWidth::W64,
3968            |x, y| {
3969                comp64(x, y, |x, y| {
3970                    // x & y != 0
3971                    if x.min != x.max || y.min != y.max {
3972                        return None;
3973                    }
3974                    Some(x.min & y.min != 0)
3975                })
3976            },
3977        )
3978    }
3979
3980    fn atomic_add<'a>(
3981        &mut self,
3982        context: &mut Self::Context<'a>,
3983        fetch: bool,
3984        dst: Register,
3985        offset: i16,
3986        src: Register,
3987    ) -> Result<(), String> {
3988        self.atomic_operation(
3989            "add32",
3990            context,
3991            DataWidth::U32,
3992            fetch,
3993            dst,
3994            offset,
3995            src,
3996            AluType::Add,
3997            |x, y| alu32(x, y, |x, y| x + y),
3998        )
3999    }
4000
4001    fn atomic_add64<'a>(
4002        &mut self,
4003        context: &mut Self::Context<'a>,
4004        fetch: bool,
4005        dst: Register,
4006        offset: i16,
4007        src: Register,
4008    ) -> Result<(), String> {
4009        self.atomic_operation(
4010            "add",
4011            context,
4012            DataWidth::U64,
4013            fetch,
4014            dst,
4015            offset,
4016            src,
4017            AluType::Add,
4018            |x, y| x + y,
4019        )
4020    }
4021
4022    fn atomic_and<'a>(
4023        &mut self,
4024        context: &mut Self::Context<'a>,
4025        fetch: bool,
4026        dst: Register,
4027        offset: i16,
4028        src: Register,
4029    ) -> Result<(), String> {
4030        self.atomic_operation(
4031            "and32",
4032            context,
4033            DataWidth::U32,
4034            fetch,
4035            dst,
4036            offset,
4037            src,
4038            AluType::Plain,
4039            |x, y| alu32(x, y, |x, y| x & y),
4040        )
4041    }
4042
4043    fn atomic_and64<'a>(
4044        &mut self,
4045        context: &mut Self::Context<'a>,
4046        fetch: bool,
4047        dst: Register,
4048        offset: i16,
4049        src: Register,
4050    ) -> Result<(), String> {
4051        self.atomic_operation(
4052            "and",
4053            context,
4054            DataWidth::U64,
4055            fetch,
4056            dst,
4057            offset,
4058            src,
4059            AluType::Plain,
4060            |x, y| x & y,
4061        )
4062    }
4063
4064    fn atomic_or<'a>(
4065        &mut self,
4066        context: &mut Self::Context<'a>,
4067        fetch: bool,
4068        dst: Register,
4069        offset: i16,
4070        src: Register,
4071    ) -> Result<(), String> {
4072        self.atomic_operation(
4073            "or32",
4074            context,
4075            DataWidth::U32,
4076            fetch,
4077            dst,
4078            offset,
4079            src,
4080            AluType::Plain,
4081            |x, y| alu32(x, y, |x, y| x | y),
4082        )
4083    }
4084
4085    fn atomic_or64<'a>(
4086        &mut self,
4087        context: &mut Self::Context<'a>,
4088        fetch: bool,
4089        dst: Register,
4090        offset: i16,
4091        src: Register,
4092    ) -> Result<(), String> {
4093        self.atomic_operation(
4094            "or",
4095            context,
4096            DataWidth::U64,
4097            fetch,
4098            dst,
4099            offset,
4100            src,
4101            AluType::Plain,
4102            |x, y| x | y,
4103        )
4104    }
4105
4106    fn atomic_xor<'a>(
4107        &mut self,
4108        context: &mut Self::Context<'a>,
4109        fetch: bool,
4110        dst: Register,
4111        offset: i16,
4112        src: Register,
4113    ) -> Result<(), String> {
4114        self.atomic_operation(
4115            "xor32",
4116            context,
4117            DataWidth::U32,
4118            fetch,
4119            dst,
4120            offset,
4121            src,
4122            AluType::Plain,
4123            |x, y| alu32(x, y, |x, y| x ^ y),
4124        )
4125    }
4126
4127    fn atomic_xor64<'a>(
4128        &mut self,
4129        context: &mut Self::Context<'a>,
4130        fetch: bool,
4131        dst: Register,
4132        offset: i16,
4133        src: Register,
4134    ) -> Result<(), String> {
4135        self.atomic_operation(
4136            "xor",
4137            context,
4138            DataWidth::U64,
4139            fetch,
4140            dst,
4141            offset,
4142            src,
4143            AluType::Plain,
4144            |x, y| x ^ y,
4145        )
4146    }
4147
4148    fn atomic_xchg<'a>(
4149        &mut self,
4150        context: &mut Self::Context<'a>,
4151        fetch: bool,
4152        dst: Register,
4153        offset: i16,
4154        src: Register,
4155    ) -> Result<(), String> {
4156        self.atomic_operation(
4157            "xchg32",
4158            context,
4159            DataWidth::U32,
4160            fetch,
4161            dst,
4162            offset,
4163            src,
4164            AluType::Plain,
4165            |_, x| x,
4166        )
4167    }
4168
4169    fn atomic_xchg64<'a>(
4170        &mut self,
4171        context: &mut Self::Context<'a>,
4172        fetch: bool,
4173        dst: Register,
4174        offset: i16,
4175        src: Register,
4176    ) -> Result<(), String> {
4177        self.raw_atomic_operation(
4178            "xchg",
4179            context,
4180            DataWidth::U64,
4181            fetch,
4182            dst,
4183            offset,
4184            src,
4185            |_, _, x| Ok(x),
4186        )
4187    }
4188
4189    fn atomic_cmpxchg<'a>(
4190        &mut self,
4191        context: &mut Self::Context<'a>,
4192        dst: Register,
4193        offset: i16,
4194        src: Register,
4195    ) -> Result<(), String> {
4196        self.raw_atomic_cmpxchg("cmpxchg32", context, dst, offset, src, JumpWidth::W32, |x, y| {
4197            comp32(x, y, |x, y| {
4198                // x == y
4199                if x.min == x.max && x.min == y.min && x.min == y.max {
4200                    return Some(true);
4201                }
4202                if x.max < y.min || y.max < x.min {
4203                    return Some(false);
4204                }
4205                None
4206            })
4207        })
4208    }
4209
4210    fn atomic_cmpxchg64<'a>(
4211        &mut self,
4212        context: &mut Self::Context<'a>,
4213        dst: Register,
4214        offset: i16,
4215        src: Register,
4216    ) -> Result<(), String> {
4217        self.raw_atomic_cmpxchg("cmpxchg", context, dst, offset, src, JumpWidth::W64, |x, y| {
4218            comp64(x, y, |x, y| {
4219                // x == y
4220                if x.min == x.max && x.min == y.min && x.min == y.max {
4221                    return Some(true);
4222                }
4223                if x.max < y.min || y.max < x.min {
4224                    return Some(false);
4225                }
4226                None
4227            })
4228        })
4229    }
4230
4231    fn load<'a>(
4232        &mut self,
4233        context: &mut Self::Context<'a>,
4234        dst: Register,
4235        offset: i16,
4236        src: Register,
4237        width: DataWidth,
4238    ) -> Result<(), String> {
4239        bpf_log!(
4240            self,
4241            context,
4242            "ldx{} {}, [{}{}]",
4243            width.str(),
4244            display_register(dst),
4245            display_register(src),
4246            print_offset(offset)
4247        );
4248        let addr = self.reg(src)?;
4249        let loaded_type = self.load_memory(context, &addr, Field::new(offset, width))?;
4250        let mut next = self.next()?;
4251        next.set_reg(dst, loaded_type)?;
4252        context.states.push(next);
4253        Ok(())
4254    }
4255
4256    fn load64<'a>(
4257        &mut self,
4258        context: &mut Self::Context<'a>,
4259        dst: Register,
4260        src: u8,
4261        lower: u32,
4262    ) -> Result<(), String> {
4263        // The pre-scan has already guaranteed that `pc + 1` is valid and structurally well-formed.
4264        let next_instruction = &context.code[self.pc + 1];
4265
4266        let value = match src {
4267            0 => {
4268                let value = (lower as u64) | (((next_instruction.imm() as u32) as u64) << 32);
4269                bpf_log!(self, context, "lddw {}, 0x{:x}", display_register(dst), value);
4270                Type::from(value)
4271            }
4272            BPF_PSEUDO_MAP_IDX => {
4273                let map_index = lower;
4274                bpf_log!(
4275                    self,
4276                    context,
4277                    "lddw {}, map_by_index({:x})",
4278                    display_register(dst),
4279                    map_index
4280                );
4281                context
4282                    .calling_context
4283                    .maps
4284                    .get(usize::try_from(map_index).unwrap())
4285                    .map(|schema| Type::ConstPtrToMap { id: map_index.into(), schema: *schema })
4286                    .ok_or_else(|| format!("lddw with invalid map index: {}", map_index))?
4287            }
4288            BPF_PSEUDO_MAP_IDX_VALUE => {
4289                let map_index = lower;
4290                let offset = next_instruction.imm();
4291                bpf_log!(
4292                    self,
4293                    context,
4294                    "lddw {}, map_value_by_index({:x})+{offset}",
4295                    display_register(dst),
4296                    map_index
4297                );
4298                let id = context.next_id();
4299                let map_schema = context
4300                    .calling_context
4301                    .maps
4302                    .get(usize::try_from(map_index).unwrap())
4303                    .ok_or_else(|| format!("lddw with invalid map index: {}", map_index))?;
4304
4305                if map_schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY {
4306                    return Err(format!(
4307                        "Invalid map type at index {map_index} for lddw. Expecting array."
4308                    ));
4309                }
4310                if map_schema.max_entries == 0 {
4311                    return Err(format!("Array has no entry."));
4312                }
4313
4314                Type::PtrToMemory {
4315                    id: MemoryId::from(id),
4316                    offset: offset.into(),
4317                    buffer_size: map_schema.value_size.into(),
4318                }
4319            }
4320            _ => {
4321                return Err(format!("invalid lddw"));
4322            }
4323        };
4324
4325        let parent = Some(Arc::new(self.clone()));
4326        let mut next = self.jump_with_offset(1, parent)?;
4327        next.set_reg(dst, value.into())?;
4328
4329        context.states.push(next);
4330        Ok(())
4331    }
4332
4333    fn load_from_packet<'a>(
4334        &mut self,
4335        context: &mut Self::Context<'a>,
4336        dst: Register,
4337        src: Register,
4338        offset: i32,
4339        register_offset: Option<Register>,
4340        width: DataWidth,
4341    ) -> Result<(), String> {
4342        bpf_log!(
4343            self,
4344            context,
4345            "ldp{} {}{}",
4346            width.str(),
4347            register_offset.map(display_register).unwrap_or_else(Default::default),
4348            print_offset(offset)
4349        );
4350
4351        // Verify that `src` refers to a packet.
4352        let src_type = self.reg(src)?;
4353        let src_is_packet = match &context.calling_context.packet_type {
4354            Some(packet_type) => src_type == *packet_type,
4355            None => false,
4356        };
4357        if !src_is_packet {
4358            return Err(format!("R{} is not a packet", src));
4359        }
4360
4361        if let Some(reg) = register_offset {
4362            let reg = self.reg(reg)?;
4363            if !reg.is_written_scalar() {
4364                return Err("access to unwritten offset".to_string());
4365            }
4366        }
4367        // Handle the case where the load succeed.
4368        let mut next = self.next()?;
4369        next.set_reg(dst, Type::UNKNOWN_SCALAR)?;
4370        for i in 1..=5 {
4371            next.set_reg(i, Type::default())?;
4372        }
4373        context.states.push(next);
4374        // Handle the case where the load fails.
4375        if !self.reg(0)?.is_written_scalar() {
4376            return Err("register 0 is incorrect at exit time".to_string());
4377        }
4378        if !self.resources.is_empty() {
4379            return Err("some resources have not been released at exit time".to_string());
4380        }
4381        self.terminated = true;
4382        Ok(())
4383    }
4384
4385    fn store<'a>(
4386        &mut self,
4387        context: &mut Self::Context<'a>,
4388        dst: Register,
4389        offset: i16,
4390        src: Source,
4391        width: DataWidth,
4392    ) -> Result<(), String> {
4393        let value = match src {
4394            Source::Reg(r) => {
4395                bpf_log!(
4396                    self,
4397                    context,
4398                    "stx{} [{}{}], {}",
4399                    width.str(),
4400                    display_register(dst),
4401                    print_offset(offset),
4402                    display_register(r),
4403                );
4404                self.reg(r)?
4405            }
4406            Source::Value(v) => {
4407                bpf_log!(
4408                    self,
4409                    context,
4410                    "st{} [{}{}], 0x{:x}",
4411                    width.str(),
4412                    display_register(dst),
4413                    print_offset(offset),
4414                    v,
4415                );
4416                Type::from(v & Type::mask(width))
4417            }
4418        };
4419        let mut next = self.next()?;
4420        let addr = self.reg(dst)?;
4421        next.store_memory(context, &addr, Field::new(offset, width), value)?;
4422        context.states.push(next);
4423        Ok(())
4424    }
4425}
4426
4427fn alu32(
4428    x: ScalarValueData,
4429    y: ScalarValueData,
4430    op: impl FnOnce(U32ScalarValueData, U32ScalarValueData) -> U32ScalarValueData,
4431) -> ScalarValueData {
4432    op(U32ScalarValueData::from(x), U32ScalarValueData::from(y)).into()
4433}
4434
4435fn comp64(
4436    x: ScalarValueData,
4437    y: ScalarValueData,
4438    op: impl FnOnce(U64Range, U64Range) -> Option<bool>,
4439) -> Result<Option<bool>, ()> {
4440    if !x.is_fully_initialized() || !y.is_fully_initialized() {
4441        return Err(());
4442    }
4443    Ok(op(x.urange, y.urange))
4444}
4445
4446fn comp32(
4447    x: ScalarValueData,
4448    y: ScalarValueData,
4449    op: impl FnOnce(U32Range, U32Range) -> Option<bool>,
4450) -> Result<Option<bool>, ()> {
4451    let x = U32ScalarValueData::from(x);
4452    let y = U32ScalarValueData::from(y);
4453    if !x.is_fully_initialized() || !y.is_fully_initialized() {
4454        return Err(());
4455    }
4456    Ok(op(x.urange, y.urange))
4457}
4458
4459fn scomp64(
4460    x: ScalarValueData,
4461    y: ScalarValueData,
4462    op: impl FnOnce(i64, i64) -> bool,
4463) -> Result<Option<bool>, ()> {
4464    if !x.is_fully_initialized() || !y.is_fully_initialized() {
4465        return Err(());
4466    }
4467    if !x.is_known() || !y.is_known() {
4468        return Ok(None);
4469    }
4470    Ok(Some(op(x.value as i64, y.value as i64)))
4471}
4472
4473fn scomp32(
4474    x: ScalarValueData,
4475    y: ScalarValueData,
4476    op: impl FnOnce(i32, i32) -> bool,
4477) -> Result<Option<bool>, ()> {
4478    let x = U32ScalarValueData::from(x);
4479    let y = U32ScalarValueData::from(y);
4480    if !x.is_fully_initialized() || !y.is_fully_initialized() {
4481        return Err(());
4482    }
4483    if !x.is_known() || !y.is_known() {
4484        return Ok(None);
4485    }
4486    Ok(Some(op(x.value as i32, y.value as i32)))
4487}
4488
4489fn print_offset<T: Into<i32>>(offset: T) -> String {
4490    let offset: i32 = offset.into();
4491    if offset == 0 {
4492        String::new()
4493    } else if offset > 0 {
4494        format!("+{offset}")
4495    } else {
4496        format!("{offset}")
4497    }
4498}
4499
4500fn run_on_stack_offset<F>(v: StackOffset, f: F) -> StackOffset
4501where
4502    F: FnOnce(ScalarValueData) -> ScalarValueData,
4503{
4504    StackOffset(f(v.reg()))
4505}
4506
4507fn error_and_log<T>(
4508    logger: &mut dyn VerifierLogger,
4509    msg: impl std::string::ToString,
4510) -> Result<T, EbpfError> {
4511    let msg = msg.to_string();
4512    logger.log(msg.as_bytes());
4513    return Err(EbpfError::ProgramVerifyError(msg));
4514}
4515
4516fn associate_orderings(o1: Ordering, o2: Ordering) -> Option<Ordering> {
4517    match (o1, o2) {
4518        (o1, o2) if o1 == o2 => Some(o1),
4519        (o, Ordering::Equal) | (Ordering::Equal, o) => Some(o),
4520        _ => None,
4521    }
4522}
4523
4524#[cfg(test)]
4525mod tests {
4526    use super::*;
4527    use std::collections::BTreeSet;
4528    use test_util::{assert_geq, assert_leq};
4529
4530    #[test]
4531    fn test_type_ordering() {
4532        let t0 = Type::from(0);
4533        let t1 = Type::from(1);
4534        let random = Type::AliasParameter { parameter_index: 8 };
4535        let unknown_written = Type::UNKNOWN_SCALAR;
4536        let unwritten = Type::default();
4537
4538        assert_eq!(t0.partial_cmp(&t0), Some(Ordering::Equal));
4539        assert_eq!(t0.partial_cmp(&t1), None);
4540        assert_eq!(t0.partial_cmp(&random), None);
4541        assert_eq!(t0.partial_cmp(&unknown_written), Some(Ordering::Less));
4542        assert_eq!(t0.partial_cmp(&unwritten), Some(Ordering::Less));
4543
4544        assert_eq!(t1.partial_cmp(&t0), None);
4545        assert_eq!(t1.partial_cmp(&t1), Some(Ordering::Equal));
4546        assert_eq!(t1.partial_cmp(&random), None);
4547        assert_eq!(t1.partial_cmp(&unknown_written), Some(Ordering::Less));
4548        assert_eq!(t1.partial_cmp(&unwritten), Some(Ordering::Less));
4549
4550        assert_eq!(random.partial_cmp(&t0), None);
4551        assert_eq!(random.partial_cmp(&t1), None);
4552        assert_eq!(random.partial_cmp(&random), Some(Ordering::Equal));
4553        assert_eq!(random.partial_cmp(&unknown_written), None);
4554        assert_eq!(random.partial_cmp(&unwritten), Some(Ordering::Less));
4555
4556        assert_eq!(unknown_written.partial_cmp(&t0), Some(Ordering::Greater));
4557        assert_eq!(unknown_written.partial_cmp(&t1), Some(Ordering::Greater));
4558        assert_eq!(unknown_written.partial_cmp(&random), None);
4559        assert_eq!(unknown_written.partial_cmp(&unknown_written), Some(Ordering::Equal));
4560        assert_eq!(unknown_written.partial_cmp(&unwritten), Some(Ordering::Less));
4561
4562        assert_eq!(unwritten.partial_cmp(&t0), Some(Ordering::Greater));
4563        assert_eq!(unwritten.partial_cmp(&t1), Some(Ordering::Greater));
4564        assert_eq!(unwritten.partial_cmp(&random), Some(Ordering::Greater));
4565        assert_eq!(unwritten.partial_cmp(&unknown_written), Some(Ordering::Greater));
4566        assert_eq!(unwritten.partial_cmp(&unwritten), Some(Ordering::Equal));
4567    }
4568
4569    #[test]
4570    fn test_stack_ordering() {
4571        let mut s1 = Stack::default();
4572        let mut s2 = Stack::default();
4573
4574        assert_eq!(s1.partial_cmp(&s2), Some(Ordering::Equal));
4575        s1.set(0, 0.into());
4576        assert_eq!(s1.partial_cmp(&s2), Some(Ordering::Less));
4577        assert_eq!(s2.partial_cmp(&s1), Some(Ordering::Greater));
4578        s2.set(1, 1.into());
4579        assert_eq!(s1.partial_cmp(&s2), None);
4580        assert_eq!(s2.partial_cmp(&s1), None);
4581    }
4582
4583    #[test]
4584    fn test_context_ordering() {
4585        let mut c1 = ComputationContext::default();
4586        let mut c2 = ComputationContext::default();
4587
4588        assert_eq!(c1.partial_cmp(&c2), Some(Ordering::Equal));
4589
4590        c1.array_bounds.insert(1.into(), 5);
4591        assert_eq!(c1.partial_cmp(&c2), Some(Ordering::Less));
4592        assert_eq!(c2.partial_cmp(&c1), Some(Ordering::Greater));
4593
4594        c2.array_bounds.insert(1.into(), 7);
4595        assert_eq!(c1.partial_cmp(&c2), Some(Ordering::Greater));
4596        assert_eq!(c2.partial_cmp(&c1), Some(Ordering::Less));
4597
4598        c1.array_bounds.insert(2.into(), 9);
4599        assert_eq!(c1.partial_cmp(&c2), None);
4600        assert_eq!(c2.partial_cmp(&c1), None);
4601
4602        c2.array_bounds.insert(2.into(), 9);
4603        assert_eq!(c1.partial_cmp(&c2), Some(Ordering::Greater));
4604        assert_eq!(c2.partial_cmp(&c1), Some(Ordering::Less));
4605
4606        c2.array_bounds.insert(3.into(), 12);
4607        assert_eq!(c1.partial_cmp(&c2), Some(Ordering::Greater));
4608        assert_eq!(c2.partial_cmp(&c1), Some(Ordering::Less));
4609
4610        c1.pc = 8;
4611        assert_eq!(c1.partial_cmp(&c2), None);
4612        assert_eq!(c2.partial_cmp(&c1), None);
4613    }
4614
4615    #[test]
4616    fn test_stack_access() {
4617        let mut s = Stack::default();
4618
4619        // Store data in the range [8, 26) and verify that `read_data_ptr()` fails for any
4620        // reads outside of that range.
4621        assert!(s.store(StackOffset(8.into()), Type::UNKNOWN_SCALAR, DataWidth::U64).is_ok());
4622        assert!(s.store(StackOffset(16.into()), Type::UNKNOWN_SCALAR, DataWidth::U64).is_ok());
4623        assert!(s.store(StackOffset(24.into()), Type::UNKNOWN_SCALAR, DataWidth::U16).is_ok());
4624
4625        for offset in 0..32 {
4626            for end in (offset + 1)..32 {
4627                assert_eq!(
4628                    s.read_data_ptr(2, StackOffset(offset.into()), (end - offset) as u64).is_ok(),
4629                    offset >= 8 && end <= 26
4630                );
4631            }
4632        }
4633
4634        // Verify that overflows are handled properly.
4635        assert!(s.read_data_ptr(2, StackOffset(12.into()), u64::MAX - 2).is_err());
4636    }
4637
4638    #[test]
4639    fn test_compute_range_for_bytes_swap() {
4640        // Build a list of interesting values. Interesting values are all possible combination of
4641        // bytes being either 0, max value, or an intermediary value.
4642        let mut values = BTreeSet::<u64>::default();
4643        for v1 in &[0x00, 0x1, u64::MAX] {
4644            for v2 in &[0x00, 0x1, u64::MAX] {
4645                for v3 in &[0x00, 0x1, u64::MAX] {
4646                    values.insert(U64Range::assemble_slices((*v1, *v2, *v3), 1, 1));
4647                }
4648            }
4649        }
4650        // Replace the second byte of old by the first byte of new and return the result.
4651        let store = |old: u64, new: u64| (old & !0xff00) | ((new & 0xff) << 8);
4652
4653        for old in &values {
4654            for new in &values {
4655                let s = store(*old, *new);
4656                for min_old in values.iter().filter(|v| *v <= old) {
4657                    for min_new in values.iter().filter(|v| *v <= new) {
4658                        for max_old in values.iter().filter(|v| *v >= old) {
4659                            for max_new in values.iter().filter(|v| *v >= new) {
4660                                let range = U64Range::compute_range_for_bytes_swap(
4661                                    U64Range::new(*min_old, *max_old),
4662                                    U64Range::new(*min_new, *max_new),
4663                                    1,
4664                                    0,
4665                                    1,
4666                                );
4667                                assert_leq!(range.min, s);
4668                                assert_geq!(range.max, s);
4669                            }
4670                        }
4671                    }
4672                }
4673            }
4674        }
4675    }
4676
4677    #[test]
4678    fn test_type_constraint_ge_gt() {
4679        let mut context = ComputationContext::default();
4680        let (new_lhs, new_rhs) = Type::constraint(
4681            &mut context,
4682            JumpType::Ge,
4683            JumpWidth::W64,
4684            Type::UNKNOWN_SCALAR,
4685            Type::from(10),
4686        )
4687        .unwrap();
4688
4689        if let Type::ScalarValue(data1) = new_lhs {
4690            assert_eq!(data1.min(), 10);
4691            assert_eq!(data1.max(), u64::MAX);
4692        } else {
4693            panic!("Expected ScalarValue");
4694        }
4695
4696        if let Type::ScalarValue(data2) = new_rhs {
4697            assert_eq!(data2.min(), 10);
4698            assert_eq!(data2.max(), 10);
4699        } else {
4700            panic!("Expected ScalarValue");
4701        }
4702
4703        let (new_lhs, _) = Type::constraint(
4704            &mut context,
4705            JumpType::Gt,
4706            JumpWidth::W64,
4707            Type::UNKNOWN_SCALAR,
4708            Type::from(10),
4709        )
4710        .unwrap();
4711
4712        if let Type::ScalarValue(data1) = new_lhs {
4713            assert_eq!(data1.min(), 11);
4714            assert_eq!(data1.max(), u64::MAX);
4715        } else {
4716            panic!("Expected ScalarValue");
4717        }
4718    }
4719
4720    #[test]
4721    fn test_type_constraint_lt_le() {
4722        let mut context = ComputationContext::default();
4723        let (new_lhs, _) = Type::constraint(
4724            &mut context,
4725            JumpType::Lt,
4726            JumpWidth::W64,
4727            Type::UNKNOWN_SCALAR,
4728            Type::from(10),
4729        )
4730        .unwrap();
4731
4732        if let Type::ScalarValue(data1) = new_lhs {
4733            assert_eq!(data1.min(), 0);
4734            assert_eq!(data1.max(), 9);
4735        } else {
4736            panic!("Expected ScalarValue");
4737        }
4738
4739        let (new_lhs, _) = Type::constraint(
4740            &mut context,
4741            JumpType::Le,
4742            JumpWidth::W64,
4743            Type::UNKNOWN_SCALAR,
4744            Type::from(10),
4745        )
4746        .unwrap();
4747
4748        if let Type::ScalarValue(data1) = new_lhs {
4749            assert_eq!(data1.min(), 0);
4750            assert_eq!(data1.max(), 10);
4751        } else {
4752            panic!("Expected ScalarValue");
4753        }
4754    }
4755
4756    #[test]
4757    fn test_type_constraint_w32() {
4758        let mut context = ComputationContext::default();
4759        let (new_lhs, _) = Type::constraint(
4760            &mut context,
4761            JumpType::Eq,
4762            JumpWidth::W32,
4763            Type::ScalarValue(ScalarValueData::UNKNOWN_WRITTEN),
4764            Type::from(10),
4765        )
4766        .unwrap();
4767        if let Type::ScalarValue(data1) = new_lhs {
4768            assert_eq!(data1.min(), 10);
4769            assert_eq!(data1.max(), 0xffff_ffff_0000_000a);
4770        } else {
4771            panic!("Expected ScalarValue");
4772        }
4773    }
4774
4775    #[test]
4776    fn test_type_constraint_w32_straddling_min() {
4777        // A 32-bit equality only constrains the low 32 bits. When the comparison
4778        // partner's range straddles 2^32 (min <= U32_MAX < max), its low 32 bits
4779        // can be smaller than its minimum, so the other register's minimum must
4780        // not be raised. `data1` is in [0, 0x40]; `data2` is in [0x30, u64::MAX].
4781        // On the equal branch `data1` can still be 0 (e.g. when `data2 == 2^32`,
4782        // whose low 32 bits are 0), so its minimum must remain 0.
4783        let mut context = ComputationContext::default();
4784        let data1 = ScalarValueData::new(0, u64::MAX, 0, U64Range::new(0, 0x40));
4785        let data2 = ScalarValueData::new(0, u64::MAX, 0, U64Range::new(0x30, u64::MAX));
4786        let (new_lhs, _) = Type::constraint(
4787            &mut context,
4788            JumpType::Eq,
4789            JumpWidth::W32,
4790            Type::ScalarValue(data1),
4791            Type::ScalarValue(data2),
4792        )
4793        .unwrap();
4794        if let Type::ScalarValue(data) = new_lhs {
4795            assert_eq!(data.min(), 0);
4796            assert_eq!(data.max(), 0x40);
4797        } else {
4798            panic!("Expected ScalarValue");
4799        }
4800    }
4801
4802    #[test]
4803    fn test_type_constraint_null_or() {
4804        let mut context = ComputationContext::default();
4805        let id = MemoryId::new();
4806        let null_or = Type::NullOr { id: id.clone(), inner: Box::new(Type::UNKNOWN_SCALAR) };
4807        let (new_lhs, _) =
4808            Type::constraint(&mut context, JumpType::Eq, JumpWidth::W64, null_or, Type::from(0))
4809                .unwrap();
4810        if let Type::ScalarValue(data) = new_lhs {
4811            assert_eq!(data.value, 0);
4812        } else {
4813            panic!("Expected zero ScalarValue");
4814        }
4815    }
4816
4817    #[test]
4818    fn test_type_constraint_array_bounds() {
4819        let mut context = ComputationContext::default();
4820        let id = MemoryId::new();
4821        let _ = Type::constraint(
4822            &mut context,
4823            JumpType::Le,
4824            JumpWidth::W64,
4825            Type::PtrToArray { id: id.clone(), offset: 22.into() },
4826            Type::PtrToEndArray { id: id.clone() },
4827        )
4828        .unwrap();
4829        assert_eq!(context.array_bounds.get(&id), Some(&22));
4830    }
4831
4832    fn make_verification_context<'a>(
4833        logger: &'a mut dyn VerifierLogger,
4834    ) -> VerificationContext<'a> {
4835        VerificationContext::new(CallingContext::default(), logger, &[], vec![])
4836    }
4837
4838    /// Verifies that multiple invocations returning PtrToMemory receive
4839    /// distinct MemoryIds to prevent ASLR pointer arithmetic between separate
4840    /// allocations, while still matching the static parameter templates.
4841    #[test]
4842    fn test_resolve_return_value_unique_ids() {
4843        let mut logger = NullVerifierLogger;
4844        let mut verification_context = make_verification_context(&mut logger);
4845        let comp_ctx = ComputationContext::default();
4846
4847        let static_id = MemoryId::from_raw(42);
4848        let ret_template =
4849            Type::PtrToMemory { id: static_id.clone(), offset: 0.into(), buffer_size: 64 };
4850
4851        let mut next1 = ComputationContext::default();
4852        let t1 = comp_ctx
4853            .resolve_return_value(&mut verification_context, &ret_template, &mut next1, false)
4854            .unwrap();
4855        let mut next2 = ComputationContext::default();
4856        let t2 = comp_ctx
4857            .resolve_return_value(&mut verification_context, &ret_template, &mut next2, false)
4858            .unwrap();
4859
4860        assert_ne!(t1, t2);
4861        let (Type::PtrToMemory { id: id1, .. }, Type::PtrToMemory { id: id2, .. }) = (&t1, &t2)
4862        else {
4863            panic!("Expected PtrToMemory");
4864        };
4865        assert_ne!(id1, id2);
4866        assert!(static_id.matches(id1));
4867        assert!(static_id.matches(id2));
4868    }
4869
4870    /// Verifies that multiple releasable allocations are tracked individually
4871    /// in resources, releasing one does not remove the other, and passing a
4872    /// released resource to a helper parameter is rejected.
4873    #[test]
4874    fn test_resolve_return_value_releasable_resource_tracking() {
4875        let mut logger = NullVerifierLogger;
4876        let mut verification_context = make_verification_context(&mut logger);
4877        let comp_ctx = ComputationContext::default();
4878
4879        let static_id = MemoryId::from_raw(99);
4880        let releasable_template = Type::ReleasableParameter {
4881            id: static_id.clone(),
4882            inner: Box::new(Type::PtrToMemory {
4883                id: static_id.clone(),
4884                offset: 0.into(),
4885                buffer_size: 64,
4886            }),
4887        };
4888
4889        let mut next = ComputationContext::default();
4890        let t1 = comp_ctx
4891            .resolve_return_value(&mut verification_context, &releasable_template, &mut next, false)
4892            .unwrap();
4893        let t2 = comp_ctx
4894            .resolve_return_value(&mut verification_context, &releasable_template, &mut next, false)
4895            .unwrap();
4896
4897        assert_eq!(next.resources.len(), 2);
4898        let (Type::Releasable { id: id1, .. }, Type::Releasable { id: id2, .. }) = (&t1, &t2)
4899        else {
4900            panic!("Expected Releasable");
4901        };
4902        assert_ne!(id1, id2);
4903        assert!(next.resources.contains(id1));
4904        assert!(next.resources.contains(id2));
4905
4906        // Release one
4907        let mut next_after_free = next.clone();
4908        t1.match_parameter_type(
4909            &verification_context,
4910            &comp_ctx,
4911            "test_free",
4912            &Type::ReleaseParameter { id: static_id.clone() },
4913            0,
4914            &mut next_after_free,
4915        )
4916        .unwrap();
4917        assert_eq!(next_after_free.resources.len(), 1);
4918        assert!(!next_after_free.resources.contains(id1));
4919        assert!(next_after_free.resources.contains(id2));
4920
4921        // Passing released t1 to general helper must fail
4922        let mut next_uaf = next_after_free.clone();
4923        let err = t1.match_parameter_type(
4924            &verification_context,
4925            &comp_ctx,
4926            "test_helper",
4927            &Type::MemoryParameter {
4928                size: MemoryParameterSize::Value(64),
4929                input: true,
4930                output: false,
4931            },
4932            0,
4933            &mut next_uaf,
4934        );
4935        assert!(err.is_err());
4936        assert_eq!(err.unwrap_err(), "Resource already released for index 0");
4937    }
4938}