public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Robert Obkircher <r.obkircher@proxmox.com>
To: "Max R. Carrara" <m.carrara@proxmox.com>
Cc: pbs-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox-backup 1/2] pbs-tape: fix undefined behavior in block header deallocation
Date: Thu, 06 Aug 2026 12:57:11 +0200	[thread overview]
Message-ID: <178601383145.99099.751352518821996428.b4-review@b4> (raw)
In-Reply-To: <20260805141620.4190773-2-m.carrara@proxmox.com>

> The `BlockHeader` struct in `pbs-tape` is a dynamically sized type
> whose data we allocate with an alignment equal to the page size.
> However, since `BlockHeader` uses `#[repr(C, packed)]`, the Rust
> compiler will always treat the type as having an alignment of 1
> instead. This is undefined behavior as reported by miri [0], since
> Rust requires the alignment used during deallocation to match that
> used during allocation, even if the underlying allocator doesn't
> actually care about alignment on `free()`.
> 
> While this should be harmless with the current standard library on
> *nix and WASM, there is no guarantee that this won't change in the
> future. If we were to target Windows, it would already be a problem [1].
> 
> To solve this, promote `BlockHeader` to a smart pointer instead, which
> manages the allocation of a new `BlockHeaderData` struct, whose
> purpose is to hold the actual data. `BlockHeaderData` has the exact
> same field ordering, layout and alignment as the previous
> implementation of `BlockHeader`, but is confined to a private module
> and then re-exported to prevent accessing its fields directly.
> 
> For convenience, move any "metadata" fields into a newly introduced
> `BlockHeaderMeta`, which is not exposed publicly. This makes computing
> the payload's size more explicit, as we can now subtract
> `size_of::<BlockHeaderMeta>()` from the total block size instead of
> using a hard-coded numeric constant.
> 
> While the data format is unlikely to change -- and if it does, we
> would need a new struct with a new magic constant for the anyways --
> this nevertheless should make understanding the code easier for future
> readers, in particular because dynamically sized types are not that
> trivial if one is not used to them.
> 
> Furthermore, store the layout used for block header allocations using
> a `LazyLock`, which allows us to avoid querying the currently used
> page size on each new block allocation. We can do this because the
> page size is a constant [2].
> 
> Additionally, implement various helper methods on `BlockHeaderData`
> that make handling the block header a little less error-prone.
> Notably, `unsafe` calls to `slice::from_raw_parts` and
> `slice::from_raw_parts_mut` are implemented as methods instead of
> being called directly in the reader / writer code.
> 
> Finally, sites using `BlockHeader` are adapted corresponding to the
> changes above, which mainly means that direct field accesses are
> replaced with method calls in most cases.
> 
> [0]: https://github.com/rust-lang/miri
> [1]: https://github.com/rust-lang/rust/blob/c9ff496891c278ad660bc0ab85c1f0b72059464a/library/std/src/sys/alloc/windows.rs#L182
> [2]: `man 3 sysconf`
> 
> Reported-by: Robert Obkircher <r.obkircher@proxmox.com>
> Signed-off-by: Max R. Carrara <m.carrara@proxmox.com>
>
> diff --git a/pbs-tape/src/blocked_reader.rs b/pbs-tape/src/blocked_reader.rs
> index 22803371c..33c5db780 100644
> --- a/pbs-tape/src/blocked_reader.rs
> +++ b/pbs-tape/src/blocked_reader.rs
> @@ -18,7 +18,7 @@ use crate::{
>  /// the end of the stream).
>  pub struct BlockedReader<R> {
>      reader: R,
> -    buffer: Box<BlockHeader>,
> +    buffer: BlockHeader,
>      seq_nr: u32,
>      found_end_marker: bool,
>      incomplete: bool,
> @@ -33,7 +33,7 @@ impl<R: BlockRead> BlockedReader<R> {
>      /// This tries to read the first block. Please inspect the error
>      /// to detect EOF and EOT.
>      pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
> -        let mut buffer = BlockHeader::new();
> +        let mut buffer = BlockHeader::new()?;
>  
>          Self::read_block_frame(&mut buffer, &mut reader)?;
>  
> @@ -43,7 +43,7 @@ impl<R: BlockRead> BlockedReader<R> {
>          let mut got_eod = false;
>  
>          if found_end_marker {
> -            incomplete = buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
> +            incomplete = buffer.flags().contains(BlockHeaderFlags::INCOMPLETE);
>              Self::consume_eof_marker(&mut reader)?;
>              got_eod = true;
>          }
> @@ -61,7 +61,7 @@ impl<R: BlockRead> BlockedReader<R> {
>      }
>  
>      fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
> -        if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
> +        if buffer.magic() != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
>              proxmox_lang::io_bail!(
>                  "got tape block with unknown magic number - not written by PBS or incompatible LTO version"
>              );
> @@ -76,13 +76,13 @@ impl<R: BlockRead> BlockedReader<R> {
>          }
>  
>          let size = buffer.size();
> -        let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM);
> +        let found_end_marker = buffer.flags().contains(BlockHeaderFlags::END_OF_STREAM);
>  
> -        if size > buffer.payload.len() {
> +        if size > buffer.payload().len() {
>              proxmox_lang::io_bail!(
>                  "detected tape block with wrong payload size ({} > {}",
>                  size,
> -                buffer.payload.len()
> +                buffer.payload().len()
>              );
>          } else if size == 0 && !found_end_marker {
>              proxmox_lang::io_bail!("detected tape block with zero payload size");
> @@ -92,14 +92,7 @@ impl<R: BlockRead> BlockedReader<R> {
>      }
>  
>      fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> {
> -        let data = unsafe {
> -            std::slice::from_raw_parts_mut(
> -                (buffer as *mut BlockHeader) as *mut u8,
> -                BlockHeader::SIZE,
> -            )
> -        };
> -
> -        let bytes = reader.read_block(data)?;
> +        let bytes = reader.read_block(buffer.as_bytes_mut())?;
>  
>          if bytes != BlockHeader::SIZE {
>              return Err(proxmox_lang::io_format_err!("got wrong block size").into());
> @@ -127,7 +120,7 @@ impl<R: BlockRead> BlockedReader<R> {
>              Ok(()) => { /* ok */ }
>              Err(BlockReadError::EndOfFile) => {
>                  self.got_eod = true;
> -                self.read_pos = self.buffer.payload.len();
> +                self.read_pos = self.buffer.payload().len();
>                  if !self.found_end_marker && check_end_marker {
>                      proxmox_lang::io_bail!("detected tape stream without end marker");
>                  }
> @@ -147,7 +140,7 @@ impl<R: BlockRead> BlockedReader<R> {
>          if found_end_marker {
>              // consume EOF mark
>              self.found_end_marker = true;
> -            self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
> +            self.incomplete = self.buffer.flags().contains(BlockHeaderFlags::INCOMPLETE);
>              Self::consume_eof_marker(&mut self.reader)?;
>              self.got_eod = true;
>          }
> @@ -225,8 +218,11 @@ impl<R: BlockRead> Read for BlockedReader<R> {
>              } else {
>                  rest as usize
>              };
> -            buffer[..copy_len]
> -                .copy_from_slice(&self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]);
> +
> +            let (read_start, read_end) = (self.read_pos, self.read_pos + copy_len);
> +            let payload = self.buffer.payload();
> +            buffer[..copy_len].copy_from_slice(&payload[read_start..read_end]);
> +
>              self.read_pos += copy_len;
>              Ok(copy_len)
>          }
> diff --git a/pbs-tape/src/blocked_writer.rs b/pbs-tape/src/blocked_writer.rs
> index 7380af243..57721ed4a 100644
> --- a/pbs-tape/src/blocked_writer.rs
> +++ b/pbs-tape/src/blocked_writer.rs
> @@ -9,7 +9,7 @@ use crate::{BlockHeader, BlockHeaderFlags, BlockWrite, TapeWrite};
>  /// to the underlying writer.
>  pub struct BlockedWriter<W: BlockWrite> {
>      writer: W,
> -    buffer: Box<BlockHeader>,
> +    buffer: BlockHeader,
>      buffer_pos: usize,
>      seq_nr: u32,
>      logical_end_of_media: bool,
> @@ -36,7 +36,7 @@ impl<W: BlockWrite> BlockedWriter<W> {
>      pub fn new(writer: W) -> Self {
>          Self {
>              writer,
> -            buffer: BlockHeader::new(),
> +            buffer: BlockHeader::new().expect("failed to create block header"),
>              buffer_pos: 0,
>              seq_nr: 0,
>              logical_end_of_media: false,
> @@ -46,13 +46,7 @@ impl<W: BlockWrite> BlockedWriter<W> {
>      }
>  
>      fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result<bool, std::io::Error> {
> -        let data = unsafe {
> -            std::slice::from_raw_parts(
> -                (buffer as *const BlockHeader) as *const u8,
> -                BlockHeader::SIZE,
> -            )
> -        };
> -        writer.write_block(data)
> +        writer.write_block(buffer.as_bytes())
>      }
>  
>      fn write_eof(&mut self) -> Result<(), std::io::Error> {
> @@ -69,16 +63,20 @@ impl<W: BlockWrite> BlockedWriter<W> {
>              return Ok(0);
>          }
>  
> -        let rest = self.buffer.payload.len() - self.buffer_pos;
> +        let rest = self.buffer.payload().len() - self.buffer_pos;
>          let bytes = if data.len() < rest { data.len() } else { rest };
> -        self.buffer.payload[self.buffer_pos..(self.buffer_pos + bytes)]
> -            .copy_from_slice(&data[..bytes]);
> +
> +        let (write_start, write_end) = (self.buffer_pos, self.buffer_pos + bytes);
> +
> +        let payload = self.buffer.payload_mut();
> +        payload[write_start..write_end].copy_from_slice(&data[..bytes]);
>  
>          let rest = rest - bytes;
>  
>          if rest == 0 {
> -            self.buffer.flags = BlockHeaderFlags::empty();
> -            self.buffer.set_size(self.buffer.payload.len());
> +            self.buffer.set_flags(BlockHeaderFlags::empty());
> +            let payload_len = self.buffer.payload().len();
> +            self.buffer.set_size(payload_len);
>              self.buffer.set_seq_nr(self.seq_nr);
>              self.seq_nr += 1;
>              let leom = Self::write_block(&self.buffer, &mut self.writer)?;
> @@ -115,10 +113,13 @@ impl<W: BlockWrite> TapeWrite for BlockedWriter<W> {
>      /// Note: This may write an empty block just including the
>      /// END_OF_STREAM flag.
>      fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> {
> -        vec::clear(&mut self.buffer.payload[self.buffer_pos..]);
> -        self.buffer.flags = BlockHeaderFlags::END_OF_STREAM;
> +        let payload = self.buffer.payload_mut();
> +        vec::clear(&mut payload[self.buffer_pos..]);
> +
> +        self.buffer.set_flags(BlockHeaderFlags::END_OF_STREAM);
>          if incomplete {
> -            self.buffer.flags |= BlockHeaderFlags::INCOMPLETE;
> +            let flags = self.buffer.flags() | BlockHeaderFlags::INCOMPLETE;
> +            self.buffer.set_flags(flags);
>          }
>          self.buffer.set_size(self.buffer_pos);
>          self.buffer.set_seq_nr(self.seq_nr);
> diff --git a/pbs-tape/src/lib.rs b/pbs-tape/src/lib.rs
> index 0fe55a749..f77d83d37 100644
> --- a/pbs-tape/src/lib.rs
> +++ b/pbs-tape/src/lib.rs
> @@ -1,4 +1,8 @@
> +use std::alloc;
>  use std::collections::HashSet;
> +use std::io;
> +use std::ptr::NonNull;
> +use std::sync::LazyLock;
>  
>  use anyhow::{Error, bail};
>  use bitflags::bitflags;
> @@ -49,28 +53,148 @@ pub const PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0: [u8; 8] = [42, 5, 191, 60, 176,
>  // openssl::sha::sha256(b"Proxmox Backup MediaSet Label v1.0")
>  pub const PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0: [u8; 8] = [8, 96, 99, 249, 47, 151, 83, 216];
>  
> -/// Tape Block Header with data payload
> +static BLOCK_HEADER_ALLOC_LAYOUT: LazyLock<alloc::Layout> = LazyLock::new(|| {
> +    // See `man 3 sysconf` -- this is a constant and does therefore not change
> +    // during the lifetime of a process.
> +    // SAFETY: Should always be safe to call, and we check for errors afterwards.
> +    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
> +
> +    if page_size == -1 {
> +        panic!("failed to query PAGESIZE");
The man page also guarantees page_size > 0.

It should even be assert!(page_size >= align_of::<T>()), which is the
same thing in this case.

> +    }
> +
> +    // Aligned to PAGESIZE, so that we can use it with SG_IO
> +    let layout = alloc::Layout::from_size_align(BlockHeader::SIZE, page_size as usize)
> +        .expect("creating layout with page size alignment failed");
> +
> +    debug_assert!(layout.size() != 0, "layout has a size of zero!");
> +
> +    layout
> +});
> +
> +/// Tape Block Header with data payload.
>  ///
>  /// All tape files are written as sequence of blocks.
>  ///
> -/// Note: this struct is large, never put this on the stack!
> -/// so we use an unsized type to avoid that.
> -///
> -/// Tape data block are always read/written with a fixed size
> -/// (`PROXMOX_TAPE_BLOCK_SIZE`). But they may contain less data, so the
> -/// header has an additional size field. For streams of blocks, there
> -/// is a sequence number (`seq_nr`) which may be use for additional
> +/// Tape data blocks are always read/written with a fixed size
> +/// (`PROXMOX_TAPE_BLOCK_SIZE`), but since they may contain less data, the
> +/// header also has an additional size field. For streams of blocks, there
> +/// is a sequence number (`seq_nr`) which may be used for additional
>  /// error checking.
> -#[repr(C, packed)]
> +///
> +/// This struct acts as a smart pointer to the underlying block header
> +/// implementation, [`BlockHeaderData`], which is a dynamically sized type.
> +/// [`BlockHeaderData`]'s memory is aligned to `PAGESIZE`, but because it uses a
> +/// `packed` layout, the compiler will think its alignment is `1` instead.
> +///
> +/// Since Rust accounts for the alignment even on deallocation, this
> +/// smart pointer therefore ensures that [`BlockHeaderData`] is deallocated
> +/// with the same alignment that was used when it was allocated. Using a
> +/// different alignment would otherwise be undefined behavior, which [`miri`]
> +/// would report as such.
> +///
> +/// [`miri`]: https://github.com/rust-lang/miri
> +#[repr(transparent)]
>  pub struct BlockHeader {
> -    /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
> -    pub magic: [u8; 8],
> -    pub flags: BlockHeaderFlags,
> -    /// size as 3 bytes unsigned, little endian
> -    pub size: [u8; 3],
> -    /// block sequence number
> -    pub seq_nr: u32,
> -    pub payload: [u8],
> +    ptr: NonNull<BlockHeaderData>,
> +}
> +
> +impl BlockHeader {
> +    pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
> +
> +    // We are subtracting the metadata size first before dividing by the size of the
> +    // payload's element type, since we need to ensure that we don't mix units.
> +    //
> +    // Note that the division at the end here is actually redundant, since
> +    // size_of::<PayloadElement>() == 1, but it's better to be explicit about the arithmetic.
> +    const PAYLOAD_LEN: usize =
> +        (Self::SIZE - _impl::BLOCK_HEADER_META_SIZE) / _impl::PAYLOAD_ELEMENT_SIZE;
Imo having constants like this all over the place makes it much harder
to reason about the correctness of new.
> +
> +    pub fn new() -> Result<Self, io::Error> {
> +        // SAFETY: layout is non-zero by construction
> +        let raw_thin_ptr = unsafe { alloc::alloc_zeroed(*BLOCK_HEADER_ALLOC_LAYOUT) };
> +
> +        let Some(nonnull_thin) = NonNull::new(raw_thin_ptr) else {
> +            // NOTE: Getting a null pointer here means that we either ran out of memory, or that
> +            // our allocator doesn't support the given size or alignment. Since the latter is very,
> +            // very unlikely, we assume that we only ever hit this branch if we're OOM.
> +            return Err(io::Error::new(
> +                io::ErrorKind::OutOfMemory,
> +                "failed to allocate memory for block header",
> +            ));
io::Error::new allocates, so it might be better to just call
handle_alloc_error.
> +        };
> +
> +        // A bit of weirdness regarding dynamically sized types:
> +        //
> +        // Since BlockHeaderData is dynamically sized, we need to explicitly construct a fat
> +        // pointer for it. A fat pointer consists of a memory address plus the number of elements
> +        // that the pointee stores, called the length. Therefore, size_of::<*mut u8>() (thin) is
> +        // not the same as size_of::<*mut [u8]>() (fat), usually the latter is twice as large.
> +        //
> +        // The length that we specify here is the number of elements our trailing payload can
> +        // store, which we defined earlier as PAYLOAD_LEN. We then cast our fat *mut [u8] to
> +        // *mut BlockHeaderData -- since the length stored in a fat pointer is *not* converted or
> +        // altered when casting, we successfully gave the trailing payload slice in BlockHeaderData
> +        // a concrete size of PAYLOAD_LEN.
> +        //
> +        // This only works because the unsized payload slice is at the end of the BlockHeaderData
> +        // struct. The compiler will know what the length in the fat pointer corresponds to that
> +        // slice. (Note that a different position for the payload in the struct wouldn't work, and
> +        // neither would multiple slices; the compiler prevents us from doing either.)
> +        //
> +        // SAFETY: We ensured that PAYLOAD_LEN is the total size minus the size of the metadata by
> +        // construction. We also ensured that our thin pointer is not null.
> +        let raw_fat_ptr =
> +            core::ptr::slice_from_raw_parts_mut(nonnull_thin.as_ptr(), Self::PAYLOAD_LEN)
> +                as *mut _impl::BlockHeaderData;

> +
> +        // SAFETY: The pointer returned by slice_from_raw_parts_mut() is never null.
> +        let mut ptr = unsafe { NonNull::new_unchecked(raw_fat_ptr) };
> +
> +        // SAFETY: We are allowed to convert the pointer to a reference. See impl Deref.
Why not just construct Self and write the field via DerefMut then?
> +        unsafe { ptr.as_mut().set_magic(PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0) };
> +
> +        debug_assert_eq!(unsafe { ptr.as_ref().payload().len() }, Self::PAYLOAD_LEN);
> +
> +        Ok(Self { ptr })
> +    }
> +}
> +
> +impl Drop for BlockHeader {
> +    fn drop(&mut self) {
> +        // SAFETY: We allocated ptr with the given layout earlier.
> +        unsafe { alloc::dealloc(self.ptr.as_ptr() as *mut u8, *BLOCK_HEADER_ALLOC_LAYOUT) };
> +    }
> +}
> +
> +impl std::ops::Deref for BlockHeader {
> +    type Target = _impl::BlockHeaderData;
> +
> +    fn deref(&self) -> &Self::Target {
> +        // SAFETY: Safe to convert ptr to a shared reference, since it's aligned, the data
> +        // initialized, dereferenceable, and explicitly non-null.
> +        unsafe { self.ptr.as_ref() }
> +    }
> +}
> +
> +impl std::ops::DerefMut for BlockHeader {
> +    fn deref_mut(&mut self) -> &mut Self::Target {
> +        // SAFETY: Safe to convert ptr to a unique reference, since it's aligned, the data
> +        // initialized, dereferenceable, and explicitly non-null.
The important thing is that &mut self provides mutual exclusion with
other places that dereference.
> +        unsafe { self.ptr.as_mut() }
> +    }
> +}
> +
> +impl AsRef<BlockHeaderData> for BlockHeader {
> +    fn as_ref(&self) -> &BlockHeaderData {
> +        &*self
> +    }
> +}
> +
> +impl AsMut<BlockHeaderData> for BlockHeader {
> +    fn as_mut(&mut self) -> &mut BlockHeaderData {
> +        &mut *self
> +    }
>  }
>  
>  bitflags! {
> @@ -84,6 +208,160 @@ bitflags! {
>      }
>  }
>  
> +// Putting underlying implementation in here to prevent access to private fields
This seems like a confusing, unrelated change with no benefit.

Leaving the original code as-is and simply replacing Box<T> with a new
PageAlignedBox<T> would have been so much easier to review.

> +mod _impl {
> +    use super::BlockHeaderFlags;
> +
> +    type PayloadType = u8;
> +
> +    pub(super) const BLOCK_HEADER_META_SIZE: usize = size_of::<BlockHeaderMeta>();
> +    pub(super) const PAYLOAD_ELEMENT_SIZE: usize = size_of::<PayloadType>();
> +
> +    /// Dynamically sized type that represents a tape block header.
> +    ///
> +    /// This struct should never be created manually and is instead accessed through
> +    /// its associated [`BlockHeader`] smart pointer.
> +    #[repr(C, packed)]
> +    pub struct BlockHeaderData {
> +        meta: BlockHeaderMeta,
> +        payload: [PayloadType],
> +    }
> +
> +    impl BlockHeaderData {
> +        /// Returns the magic value for the block header.
> +        pub const fn magic(&self) -> [u8; 8] {
> +            self.meta.magic()
> +        }
> +
> +        pub(super) const fn set_magic(&mut self, magic: [u8; 8]) {
> +            self.meta.magic = magic;
> +        }
> +
> +        /// Returns the currently used [`BlockHeaderFlags`].
> +        pub const fn flags(&self) -> BlockHeaderFlags {
> +            self.meta.flags()
> +        }
> +
> +        /// Sets the [`BlockHeaderFlags`] for the header.
> +        pub const fn set_flags(&mut self, flags: BlockHeaderFlags) {
> +            self.meta.set_flags(flags)
> +        }
> +
> +        /// Returns the size of the header.
> +        ///
> +        /// Note that this value is at most `2^24 - 1`, since the size is represented using 24 bits
> +        /// under the hood.
> +        pub const fn size(&self) -> usize {
> +            self.meta.size()
> +        }
> +
> +        /// Sets the size of the header.
> +        ///
> +        /// Note that the passed value will be truncated to 24 bits, since the size is represented
> +        /// using 24 bits under the hood.
> +        pub const fn set_size(&mut self, size: usize) {
> +            self.meta.set_size(size)
> +        }
> +
> +        /// Sets the sequence number of the header.
> +        pub const fn set_seq_nr(&mut self, seq_nr: u32) {
> +            self.meta.set_seq_nr(seq_nr);
> +        }
> +
> +        /// Returns the sequence number of the header.
> +        pub const fn seq_nr(&self) -> u32 {
> +            self.meta.seq_nr()
> +        }
> +
> +        /// Returns the underlying payload as a slice.
> +        pub const fn payload(&self) -> &[PayloadType] {
> +            &self.payload
> +        }
> +
> +        /// Returns the underlying payload as a mutable slice.
> +        pub const fn payload_mut(&mut self) -> &mut [PayloadType] {
> +            &mut self.payload
> +        }
> +
> +        /// Returns the entirety of [`BlockHeaderData`], meaning both metadata
> +        /// and payload, as a byte slice.
> +        pub const fn as_bytes(&self) -> &[u8] {
> +            unsafe {
> +                std::slice::from_raw_parts(
> +                    (self as *const BlockHeaderData) as *const u8,
> +                    super::BlockHeader::SIZE,
> +                )
> +            }
> +        }
> +
> +        /// Returns the entirety of [`BlockHeaderData`], meaning both metadata
> +        /// and payload, as a mutable byte slice.
> +        ///
> +        /// While this method in itself is safe, be aware that it nevertheless
> +        /// allows you to overwrite metadata in this struct.
> +        pub const fn as_bytes_mut(&mut self) -> &mut [u8] {
> +            unsafe {
> +                std::slice::from_raw_parts_mut(
> +                    (self as *mut BlockHeaderData) as *mut u8,
> +                    super::BlockHeader::SIZE,
> +                )
> +            }
> +        }
> +    }
> +
> +    /// Metadata for tape block headers, which precedes the header's payload.
> +    #[repr(C, packed)]
> +    struct BlockHeaderMeta {
> +        /// Fixed value, set to `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`.
> +        magic: [u8; 8],
> +        flags: BlockHeaderFlags,
> +        /// Size as 3 bytes unsigned, little endian.
> +        size: [u8; 3],
> +        /// Block sequence number.
> +        seq_nr: u32,
> +    }
> +
> +    impl BlockHeaderMeta {
> +        const fn magic(&self) -> [u8; 8] {
> +            self.magic
> +        }
> +
> +        const fn flags(&self) -> BlockHeaderFlags {
> +            self.flags
> +        }
> +
> +        const fn set_flags(&mut self, flags: BlockHeaderFlags) {
> +            self.flags = flags;
> +        }
> +
> +        const fn size(&self) -> usize {
> +            (self.size[0] as usize)
> +                + ((self.size[1] as usize) << 8)
> +                + ((self.size[2] as usize) << 16)
> +        }
> +
> +        const fn set_size(&mut self, size: usize) {
> +            let size = size.to_le_bytes();
> +            // Since we only need three bytes, we can copy them manually
> +            // instead --> allows this function to be const, since range index
> +            // is not yet const stable
Seems kind of pointless to make these functions const if you cannot
allocatate a value at compile time in the first place.

-- 
Robert Obkircher <r.obkircher@proxmox.com>




  reply	other threads:[~2026-08-06 10:57 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-05 14:16 [PATCH proxmox-backup 0/2] Fix Undefined Behavior in Tape Block Header Deallocation Max R. Carrara
2026-08-05 14:16 ` [PATCH proxmox-backup 1/2] pbs-tape: fix undefined behavior in block header deallocation Max R. Carrara
2026-08-06 10:57   ` Robert Obkircher [this message]
2026-08-07 12:47     ` Max R. Carrara
2026-08-05 14:16 ` [PATCH proxmox-backup 2/2] pbs-tape: rename `buffer` to `header` Max R. Carrara
2026-08-06 10:57   ` Robert Obkircher
2026-08-07 12:52     ` Max R. Carrara

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178601383145.99099.751352518821996428.b4-review@b4 \
    --to=r.obkircher@proxmox.com \
    --cc=m.carrara@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal