//
// Syd: rock-solid unikernel
// src/elf.rs: ELF parser
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
// Based in part upon Paludis' paludis/util/elf.{cc,hh} which is:
//   Copyright (c) 2007 Tiziano Müller
//   Copyright (c) 2007 David Leverton
//   SPDX-License-Identifier: GPL-2.0
// Based in part upon binutils' readelf.c which is:
//   Originally developed by Eric Youngdale <eric@andante.jic.com>
//   Modifications by Nick Clifton <nickc@redhat.com>
//   Copyright (C) 1998-2024 Free Software Foundation, Inc.
//   SPDX-License-Identifier: GPL-3.0-or-later
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Set of functions to manage parsing ELF files

use std::{
    convert::TryInto,
    fmt,
    io::{self, ErrorKind, Read, Seek, SeekFrom},
    path::Path,
};

use nix::errno::Errno;

use crate::fs::safe_open_file;

/// ELF magic number used to identify ELF files.
const ELF_MAGIC: &[u8] = b"\x7FELF";
/// Index of the ELF class byte in the ELF header.
const EI_CLASS: usize = 4;
/// Index of the endianness byte in the ELF header.
const EI_DATA: usize = 5;
/// Index of the version byte in the ELF header.
const EI_VERSION: usize = 6;
/// Value representing a 32-bit ELF file.
const ELFCLASS32: u8 = 1;
/// Value representing a 64-bit ELF file.
const ELFCLASS64: u8 = 2;
/// ELF type value representing no file type.
const ET_NONE: u16 = 0;
/// ELF type value representing an executable file.
const ET_EXEC: u16 = 2;
/// ELF type value representing a shared library or position-independent executable (PIE).
const ET_DYN: u16 = 3;
/// ELF type value representing a relocatable file.
const ET_REL: u16 = 1;
/// ELF type value representing a core file.
const ET_CORE: u16 = 4;
/// ELF type value for processor-specific semantics (low).
const ET_LOPROC: u16 = 0xff00;
/// ELF type value for processor-specific semantics (high).
const ET_HIPROC: u16 = 0xffff;
// Program header table entry unused.
//const PT_NULL: u32 = 0;
// Loadable program segment.
// const PT_LOAD: u32 = 1;
/// Dynamic linking information.
const PT_DYNAMIC: u32 = 2;
/// Program header type value for the interpreter segment (used for dynamic linking).
const PT_INTERP: u32 = 3;
// Auxiliary information.
// const PT_NOTE: u32 = 4;
// Reserved, unspecified semantics.
// const PT_SHLIB: u32 = 5;
// Entry for header table itself.
// const PT_PHDR: u32 = 6;
// Thread local storage segment.
// const PT_TLS: u32 = 7;
/// Hold permissions for the stack on GNU/Linux.
const PT_GNU_STACK: u32 = 0x6474e551;
/// Segment is executable.
const PF_X: u32 = 0x1;
/// Value representing little-endian data encoding.
const ELFDATA2LSB: u8 = 1;
/// Value representing big-endian data encoding.
const ELFDATA2MSB: u8 = 2;
/// Expected ELF version value.
const EV_CURRENT: u8 = 1;

/// Maximum number of program headers to prevent DoS attacks.
const MAX_PROGRAM_HEADERS: usize = 0x0001_0000;
/// Maximum size of program header entry to prevent DoS attacks.
const MAX_PHENT_SIZE: usize = 1024;
/// Maximum allowed size for the dynamic section to prevent DoS attacks.
const MAX_DYNAMIC_SECTION_SIZE: u64 = 16 * 1024 * 1024; // 16 MB

// DT_* constants
// const DT_NULL: u64 = 0;
const DT_FLAGS_1: u64 = 0x6fff_fffb;
const DF_1_PIE: u64 = 0x0800_0000;

/// Enum representing the executable file information.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ExecutableFile {
    /// Represents an ELF file with its class type, file type, linking type, and PIE status.
    Elf {
        /// The class type of the ELF file (32-bit or 64-bit).
        elf_type: ElfType,
        /// The file type of the ELF file (Executable, Library, etc.).
        file_type: ElfFileType,
        /// The linking type of the ELF file (Static or Dynamic), only for executables.
        linking_type: Option<LinkingType>,
        /// Indicates whether the ELF file is a PIE (Position-Independent Executable).
        pie: bool,
        /// Indicates whether the binary has executable stack.
        xs: bool,
    },
    /// Represents a script file with a shebang.
    Script,
}

/// Enum representing the ELF class type (32-bit or 64-bit).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfType {
    /// 32-bit ELF file.
    Elf32,
    /// 64-bit ELF file.
    Elf64,
}

/// Enum representing the ELF file type (Executable, Library, Relocatable, Core).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfFileType {
    /// No file type.
    None,
    /// Executable file.
    Executable,
    /// Shared object file (library).
    Library,
    /// Relocatable file.
    Relocatable,
    /// Core file.
    Core,
    /// Processor-specific file type.
    ProcessorSpecific,
    /// Unknown file type (reserved for new object file types in the future).
    Unknown,
}

/// Enum representing the ELF linking type (Static or Dynamic).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum LinkingType {
    /// Statically linked ELF file.
    Static,
    /// Dynamically linked ELF file.
    Dynamic,
}

/// Enum representing possible errors during ELF parsing.
#[derive(Debug)]
pub enum ElfError {
    /// Error indicating the file does not have a valid ELF magic number.
    BadMagic,
    /// Error indicating the ELF header is malformed.
    Malformed,
    /// Error indicating an I/O error occurred.
    IoError(io::Error),
}

impl fmt::Display for ElfError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfError::BadMagic => write!(f, "Invalid ELF magic number"),
            ElfError::Malformed => write!(f, "Malformed ELF header"),
            ElfError::IoError(e) => write!(f, "I/O error: {}", e),
        }
    }
}

impl From<io::Error> for ElfError {
    fn from(err: io::Error) -> Self {
        ElfError::IoError(err)
    }
}

impl From<ElfError> for Errno {
    fn from(err: ElfError) -> Self {
        match err {
            ElfError::BadMagic => Errno::EINVAL,
            ElfError::Malformed => Errno::EACCES,
            ElfError::IoError(e) => Errno::from_raw(e.raw_os_error().unwrap_or(Errno::EIO as i32)),
        }
    }
}

impl From<Errno> for ElfError {
    fn from(errno: Errno) -> Self {
        ElfError::IoError(io::Error::from_raw_os_error(errno as i32))
    }
}

impl fmt::Display for ExecutableFile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ExecutableFile::Elf {
                elf_type,
                file_type,
                linking_type,
                pie,
                xs,
            } => {
                let pie = if *pie { "-pie" } else { "" };
                let xs = if *xs { "-xs" } else { "" };
                if let Some(linking_type) = linking_type {
                    write!(f, "ELF:{file_type}{elf_type}-{linking_type}{pie}{xs}")
                } else {
                    write!(f, "ELF:{file_type}{elf_type}{pie}{xs}")
                }
            }
            ExecutableFile::Script => write!(f, "SCRIPT"),
        }
    }
}

impl fmt::Display for ElfType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfType::Elf32 => write!(f, "32"),
            ElfType::Elf64 => write!(f, "64"),
        }
    }
}

impl fmt::Display for ElfFileType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfFileType::None => write!(f, "none"),
            ElfFileType::Executable => write!(f, "exe"),
            ElfFileType::Library => write!(f, "lib"),
            ElfFileType::Relocatable => write!(f, "rel"),
            ElfFileType::Core => write!(f, "core"),
            ElfFileType::ProcessorSpecific => write!(f, "proc"),
            ElfFileType::Unknown => write!(f, "reserved"),
        }
    }
}

impl fmt::Display for LinkingType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            LinkingType::Static => write!(f, "static"),
            LinkingType::Dynamic => write!(f, "dynamic"),
        }
    }
}

impl ExecutableFile {
    /// Checks if the file at the given path is a script file by looking
    /// for the `#!` shebang.
    ///
    /// # Arguments
    ///
    /// * `path` - A reference to a `Path` representing the file to be
    ///            checked.
    ///
    /// # Returns
    ///
    /// A `Result` containing `true` if the file is a script, `false`
    /// otherwise, or an `ElfError` if an error occurs.
    ///
    /// # Errors
    ///
    /// This function returns `ElfError::IoError` if an I/O error
    /// occurs.
    ///
    /// # Safety
    ///
    /// This function ensures safety by handling all potential I/O
    /// errors gracefully.
    pub fn is_script_file(path: &Path) -> Result<bool, ElfError> {
        let (mut file, _) = safe_open_file(path)?;
        let mut shebang = [0u8; 2];
        file.read_exact(&mut shebang)?;
        Ok(&shebang == b"#!")
    }

    /// Checks if the file at the given path is an ELF file.
    pub fn is_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
        let mut magic = [0u8; 4];
        let mut nread = 0;
        while nread < 4 {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut magic[nread..]) {
                Ok(0) => break,
                Ok(n) => nread += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }

        if nread < 4 {
            // Short read, not a valid ELF file.
            Ok(false)
        } else {
            // Check the magic \x7FELF bytes.
            Ok(magic == ELF_MAGIC)
        }
    }

    /// Checks if the file at the given path is a valid ELF file.
    ///
    /// # Safety
    ///
    /// This function ensures safety by:
    /// - Validating ELF magic number.
    /// - Checking ELF version.
    /// - Validating endianness.
    /// - Handling all potential I/O errors gracefully.
    pub fn is_valid_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
        let mut ident = [0u8; 16]; // EI_NIDENT is 16 bytes.
        let mut nread = 0;
        while nread < 16 {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut ident[nread..]) {
                Ok(0) => break,
                Ok(n) => nread += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }

        if nread < 16 {
            // Short read, not a valid ELF file.
            return Ok(false);
        }

        // Check the magic \x7FELF bytes.
        if &ident[0..4] != ELF_MAGIC {
            return Ok(false);
        }

        // Check the ELF file version.
        if ident[EI_VERSION] != EV_CURRENT {
            return Ok(false);
        }

        // Check whether the endianness is valid.
        if ident[EI_DATA] != ELFDATA2LSB && ident[EI_DATA] != ELFDATA2MSB {
            return Ok(false);
        }

        // Check the ELF class.
        if ident[EI_CLASS] != ELFCLASS32 && ident[EI_CLASS] != ELFCLASS64 {
            return Ok(false);
        }

        Ok(true)
    }

    /// Parses an executable file from a reader and returns information
    /// about its type, file type, linking type, and PIE status.
    ///
    /// # Arguments
    ///
    /// * `reader` - A reader that implements the `Read` and `Seek`
    ///              traits.
    /// * `check_linking` - A boolean indicating whether to check for
    ///                     static or dynamic linking.
    ///
    /// # Returns
    ///
    /// A `Result` containing an `ExecutableFile` enum if successful, or
    /// an `ElfError` if an error occurs.
    ///
    /// # Errors
    ///
    /// This function returns `ElfError::BadMagic` if the file does not
    /// have a valid ELF magic number, `ElfError::Malformed` if the ELF
    /// header is malformed, and `ElfError::IoError` if an I/O error
    /// occurs.
    ///
    /// # Safety
    ///
    /// This function ensures safety by:
    /// - Checking file accessibility by attempting to open the file directly.
    /// - Validating ELF magic number and class.
    /// - Handling all potential I/O errors gracefully.
    /// - Handling endianness properly for different machine architectures.
    /// - Limiting the number of program headers to prevent DoS attacks.
    /// - Checking for script files with the `#!` shebang.
    #[allow(clippy::cognitive_complexity)]
    pub fn parse<R: Read + Seek>(
        mut reader: R,
        check_linking: bool,
    ) -> Result<ExecutableFile, ElfError> {
        let mut header = [0u8; 64];
        let mut bytes_read = 0;
        while bytes_read < header.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut header[bytes_read..]) {
                Ok(0) => break,
                Ok(n) => bytes_read += n,
                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
                Err(e) => return Err(ElfError::IoError(e)),
            }
        }

        // Check for script files
        match bytes_read {
            0 => return Err(ElfError::BadMagic),
            1 => {
                if header[0] == b'#' {
                    return Ok(ExecutableFile::Script);
                } else {
                    return Err(ElfError::BadMagic);
                }
            }
            2..=3 => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else {
                    return Err(ElfError::BadMagic);
                }
            }
            4..=63 => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else if bytes_read >= 4 && &header[0..4] != ELF_MAGIC {
                    return Err(ElfError::BadMagic);
                } else {
                    return Err(ElfError::Malformed);
                }
            }
            _ => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else if &header[0..4] != ELF_MAGIC {
                    return Err(ElfError::BadMagic);
                }
            }
        }

        // Determine endianness
        let is_big_endian = match header.get(EI_DATA) {
            Some(&ELFDATA2LSB) => false,
            Some(&ELFDATA2MSB) => true,
            _ => return Err(ElfError::Malformed),
        };

        // Determine ELF type
        let elf_type = match header.get(EI_CLASS) {
            Some(&ELFCLASS32) => ElfType::Elf32,
            Some(&ELFCLASS64) => ElfType::Elf64,
            _ => return Err(ElfError::Malformed),
        };

        // Determine ELF file type (Executable, Library, Relocatable, Core, Processor-specific, Unknown)
        let e_type = if is_big_endian {
            read_u16_be(header.get(16..18).ok_or(ElfError::Malformed)?)?
        } else {
            read_u16_le(header.get(16..18).ok_or(ElfError::Malformed)?)?
        };
        let mut file_type = match e_type {
            ET_NONE => ElfFileType::None,
            ET_EXEC => ElfFileType::Executable,
            ET_DYN => ElfFileType::Library,
            ET_REL => ElfFileType::Relocatable,
            ET_CORE => ElfFileType::Core,
            ET_LOPROC..=ET_HIPROC => ElfFileType::ProcessorSpecific,
            _ => ElfFileType::Unknown,
        };

        let mut dynamic = false; // Static,Dynamic linking
        let mut pie = false; // Position Independent Executable
        let mut xs = false; // Executable Stack

        if check_linking && matches!(file_type, ElfFileType::Executable | ElfFileType::Library) {
            // Read program headers
            let (phoff_offset, phnum_offset, phentsize_offset) = if elf_type == ElfType::Elf64 {
                (32, 56, 54)
            } else {
                (28, 44, 42)
            };
            #[allow(clippy::arithmetic_side_effects)]
            let phoff = if elf_type == ElfType::Elf64 {
                if is_big_endian {
                    read_u64_be(
                        header
                            .get(phoff_offset..phoff_offset + 8)
                            .ok_or(ElfError::Malformed)?,
                    )?
                } else {
                    read_u64_le(
                        header
                            .get(phoff_offset..phoff_offset + 8)
                            .ok_or(ElfError::Malformed)?,
                    )?
                }
            } else {
                if is_big_endian {
                    read_u32_be(
                        header
                            .get(phoff_offset..phoff_offset + 4)
                            .ok_or(ElfError::Malformed)?,
                    )?
                } else {
                    read_u32_le(
                        header
                            .get(phoff_offset..phoff_offset + 4)
                            .ok_or(ElfError::Malformed)?,
                    )?
                }
                .into()
            };
            #[allow(clippy::arithmetic_side_effects)]
            let phnum = if is_big_endian {
                read_u16_be(
                    header
                        .get(phnum_offset..phnum_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } else {
                read_u16_le(
                    header
                        .get(phnum_offset..phnum_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } as usize;
            #[allow(clippy::arithmetic_side_effects)]
            let phentsize = if is_big_endian {
                read_u16_be(
                    header
                        .get(phentsize_offset..phentsize_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } else {
                read_u16_le(
                    header
                        .get(phentsize_offset..phentsize_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } as usize;

            // Ensure the number of program headers and entry size is
            // within a reasonable limit.
            if phnum == 0
                || phnum > MAX_PROGRAM_HEADERS
                || phentsize == 0
                || phentsize > MAX_PHENT_SIZE
            {
                return Err(ElfError::Malformed);
            }

            // Allocate memory for program headers safely.
            let total_size = phnum.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
            let mut phdrs = Vec::new();
            phdrs.try_reserve(total_size).or(Err(ElfError::Malformed))?;
            phdrs.resize(total_size, 0);
            reader.seek(SeekFrom::Start(phoff))?;
            reader.read_exact(&mut phdrs)?;

            // Set to true when we find PT_GNU_STACK.
            let mut seen_gstack = false;
            // Set to true when we find PT_INTERP.
            let mut seen_interp = false;
            for i in 0..phnum {
                let offset = i.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
                let end = offset.checked_add(4).ok_or(ElfError::Malformed)?;
                if end > phdrs.len() || offset >= phdrs.len() {
                    // If the offset plus the size of the field (4
                    // bytes) exceeds the length of the program header
                    // table, it indicates that the program header entry
                    // cannot be read completely. This scenario can
                    // occur if the ELF file is valid but the headers do
                    // not cover the expected size. We break out of the
                    // loop to avoid further processing of incomplete
                    // data. This approach ensures we do not enter an
                    // infinite loop and handle the ELF file gracefully.
                    break;
                }
                let p_type = if is_big_endian {
                    read_u32_be(&phdrs[offset..end])?
                } else {
                    read_u32_le(&phdrs[offset..end])?
                };
                match p_type {
                    PT_INTERP => {
                        file_type = ElfFileType::Executable;
                        dynamic = true;
                        seen_interp = true;
                    }
                    PT_GNU_STACK => {
                        // Determine the offset to the p_flags field
                        // based on ELF type. In 64-bit ELF, p_flags is
                        // at offset 4 from the start of the program
                        // header. In 32-bit ELF, p_flags is at offset
                        // 24 from the start of the program header.
                        let flags_offset = if elf_type == ElfType::Elf64 {
                            offset.checked_add(4).ok_or(ElfError::Malformed)?
                        } else {
                            offset.checked_add(24).ok_or(ElfError::Malformed)?
                        };

                        // `p_flags` is always 4 bytes in both 32-bit
                        // and 64-bit ELF headers.
                        let flags_end = flags_offset.checked_add(4).ok_or(ElfError::Malformed)?;

                        // Check sanity of offsets.
                        if flags_end > phdrs.len() || flags_offset >= phdrs.len() {
                            break;
                        }

                        let p_flags = if is_big_endian {
                            read_u32_be(&phdrs[flags_offset..flags_end])?
                        } else {
                            read_u32_le(&phdrs[flags_offset..flags_end])?
                        };

                        if p_flags & PF_X != 0 {
                            xs = true;
                        }
                        seen_gstack = true;
                    }
                    _ => continue,
                }

                // We're only here for headers PT_{GNU_STACK,INTERP}.
                // If we've seen both, there's nothing else to do.
                if seen_gstack && seen_interp {
                    break;
                }
            }

            // In case of a missing GNU_STACK ELF header,
            // the loader defaults to an executable stack.
            if !seen_gstack {
                xs = true;
            }

            if let Some((dynamic_section, dynamic_size)) = read_dynamic_section(
                &mut reader,
                &phdrs,
                elf_type,
                is_big_endian,
                phnum,
                phentsize,
            )? {
                pie = is_pie(&dynamic_section, dynamic_size, elf_type, is_big_endian)?;
                if pie {
                    file_type = ElfFileType::Executable;
                }
            }
        }

        // Linking type should be None for libraries
        let linking_type = if file_type == ElfFileType::Library {
            None
        } else if file_type == ElfFileType::Executable {
            if dynamic {
                Some(LinkingType::Dynamic)
            } else {
                Some(LinkingType::Static)
            }
        } else {
            None
        };

        Ok(ExecutableFile::Elf {
            elf_type,
            file_type,
            linking_type,
            pie,
            xs,
        })
    }
}

// Function to determine if the file is PIE (Position Independent Executable)
fn is_pie(
    dynamic_section: &[u8],
    dynamic_size: usize,
    elf_type: ElfType,
    is_big_endian: bool,
) -> Result<bool, ElfError> {
    let entry_size = match elf_type {
        ElfType::Elf32 => 8,
        ElfType::Elf64 => 16,
    };

    for i in (0..dynamic_size).step_by(entry_size) {
        let j = i.checked_add(entry_size / 2).ok_or(ElfError::Malformed)?;
        if j > dynamic_size || i >= dynamic_size {
            // See the comment in parse().
            break;
        }
        #[allow(clippy::collapsible_else_if)]
        let d_tag = if is_big_endian {
            if elf_type == ElfType::Elf64 {
                read_u64_be(&dynamic_section[i..j])?
            } else {
                read_u32_be(&dynamic_section[i..j])?.into()
            }
        } else {
            if elf_type == ElfType::Elf64 {
                read_u64_le(&dynamic_section[i..j])?
            } else {
                read_u32_le(&dynamic_section[i..j])?.into()
            }
        };
        if d_tag == DT_FLAGS_1 {
            let k = i.checked_add(entry_size).ok_or(ElfError::Malformed)?;
            if k > dynamic_size || j >= dynamic_size {
                // See the comment in parse().
                break;
            }
            #[allow(clippy::collapsible_else_if)]
            let d_val = if is_big_endian {
                if elf_type == ElfType::Elf64 {
                    read_u64_be(&dynamic_section[j..k])?
                } else {
                    read_u32_be(&dynamic_section[j..k])?.into()
                }
            } else {
                if elf_type == ElfType::Elf64 {
                    read_u64_le(&dynamic_section[j..k])?
                } else {
                    read_u32_le(&dynamic_section[j..k])?.into()
                }
            };
            return Ok(d_val & DF_1_PIE != 0);
        }
    }
    Ok(false)
}

// Function to read the dynamic section from the ELF file
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::type_complexity)]
fn read_dynamic_section<R: Read + Seek>(
    reader: &mut R,
    phdrs: &[u8],
    elf_type: ElfType,
    is_big_endian: bool,
    phnum: usize,
    phentsize: usize,
) -> Result<Option<(Vec<u8>, usize)>, ElfError> {
    for i in 0..phnum {
        let offset = i.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
        let end = offset.checked_add(4).ok_or(ElfError::Malformed)?;
        if end > phdrs.len() || offset >= phdrs.len() {
            // See the comment in parse().
            break;
        }
        let p_type = if is_big_endian {
            read_u32_be(&phdrs[offset..end])?
        } else {
            read_u32_le(&phdrs[offset..end])?
        };
        if p_type == PT_DYNAMIC {
            let p_offset = if elf_type == ElfType::Elf64 {
                let offset_dyn_min = offset.checked_add(8).ok_or(ElfError::Malformed)?;
                let offset_dyn_max = offset.checked_add(16).ok_or(ElfError::Malformed)?;
                if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u64_be(&phdrs[offset_dyn_min..offset_dyn_max])?
                } else {
                    read_u64_le(&phdrs[offset_dyn_min..offset_dyn_max])?
                }
            } else {
                let offset_dyn_min = offset.checked_add(4).ok_or(ElfError::Malformed)?;
                let offset_dyn_max = offset.checked_add(8).ok_or(ElfError::Malformed)?;
                if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u32_be(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
                } else {
                    read_u32_le(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
                }
            };
            let p_filesz = if elf_type == ElfType::Elf64 {
                let offset_filesz_min = offset.checked_add(32).ok_or(ElfError::Malformed)?;
                let offset_filesz_max = offset.checked_add(40).ok_or(ElfError::Malformed)?;
                if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u64_be(&phdrs[offset_filesz_min..offset_filesz_max])?
                } else {
                    read_u64_le(&phdrs[offset_filesz_min..offset_filesz_max])?
                }
            } else {
                let offset_filesz_min = offset.checked_add(16).ok_or(ElfError::Malformed)?;
                let offset_filesz_max = offset.checked_add(20).ok_or(ElfError::Malformed)?;
                if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u32_be(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
                } else {
                    read_u32_le(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
                }
            };

            // Validate the size to avoid DoS attacks
            if p_filesz > MAX_DYNAMIC_SECTION_SIZE {
                return Err(ElfError::Malformed);
            }

            let file_size = reader.seek(SeekFrom::End(0))?;
            if p_offset > file_size || p_offset.saturating_add(p_filesz) > file_size {
                return Err(ElfError::Malformed);
            }

            reader.seek(SeekFrom::Start(p_offset))?;
            let mut dynamic_section = Vec::new();
            let p_filesz = usize::try_from(p_filesz).or(Err(ElfError::Malformed))?;
            dynamic_section
                .try_reserve(p_filesz)
                .or(Err(ElfError::Malformed))?;
            dynamic_section.resize(p_filesz, 0);
            reader.read_exact(&mut dynamic_section)?;

            return Ok(Some((dynamic_section, p_filesz)));
        }
    }
    Ok(None)
}

// Function to convert bytes to u16 in big-endian order.
fn read_u16_be(bytes: &[u8]) -> Result<u16, ElfError> {
    let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u16::from_be_bytes(arr))
}

// Function to convert bytes to u16 in little-endian order.
fn read_u16_le(bytes: &[u8]) -> Result<u16, ElfError> {
    let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u16::from_le_bytes(arr))
}

// Function to convert bytes to u32 in big-endian order.
fn read_u32_be(bytes: &[u8]) -> Result<u32, ElfError> {
    let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u32::from_be_bytes(arr))
}

// Function to convert bytes to u32 in little-endian order.
fn read_u32_le(bytes: &[u8]) -> Result<u32, ElfError> {
    let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u32::from_le_bytes(arr))
}

// Function to convert bytes to u64 in big-endian order.
fn read_u64_be(bytes: &[u8]) -> Result<u64, ElfError> {
    let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u64::from_be_bytes(arr))
}

// Function to convert bytes to u64 in little-endian order.
fn read_u64_le(bytes: &[u8]) -> Result<u64, ElfError> {
    let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u64::from_le_bytes(arr))
}
