binary-security-check 2.0.2

Analyzer of security features in executable binaries
use core::num::NonZero;

use crate::errors::{Error, Result};
use crate::utils::usize_align_up_offset;

const NOTE_GNU_PROPERTY_SECTION_NAME: &str = ".note.gnu.property";
const ELF_NOTE_GNU: &str = "GNU";

#[derive(Debug, Clone, Copy)]
pub(crate) struct RawGNUProperty<'bytes> {
    pr_type: u32,
    pr_data: &'bytes [u8],
}

impl<'bytes> RawGNUProperty<'bytes> {
    fn parse_all(elf: &'bytes goblin::elf::Elf, bytes: &'bytes [u8]) -> Result<Vec<Self>> {
        let u32_from_bytes = if elf.little_endian {
            u32::from_le_bytes
        } else {
            u32::from_be_bytes
        };

        let word_size = if elf.is_64 {
            unsafe { NonZero::new_unchecked(8) }
        } else {
            unsafe { NonZero::new_unchecked(4) }
        };

        Self::gnu_property_type0_data(elf, bytes)?
            .into_iter()
            .try_fold(Vec::default(), |mut acc, mut property| {
                while !property.is_empty() {
                    let raw = Self::parse(&mut property, word_size, u32_from_bytes)?;
                    acc.push(raw);
                }
                Ok(acc)
            })
    }

    fn parse(
        data: &mut &'bytes [u8],
        word_size: NonZero<usize>,
        u32_from_bytes: fn([u8; 4]) -> u32,
    ) -> Result<Self> {
        let pr_type = <[u8; 4]>::try_from(&data[..4]).map(u32_from_bytes)?;

        let pr_datasz = <[u8; 4]>::try_from(&data[4..8])
            .map(u32_from_bytes)?
            .try_into()?;

        let result = RawGNUProperty {
            pr_type,
            pr_data: &data[8..8_usize.saturating_add(pr_datasz)],
        };

        let pr_padding_size = usize_align_up_offset(pr_datasz, word_size);

        let property_size = 8_usize
            .saturating_add(pr_datasz)
            .saturating_add(pr_padding_size);

        *data = &data[property_size..];
        Ok(result)
    }

    fn gnu_property_type0_data(
        elf: &'bytes goblin::elf::Elf,
        bytes: &'bytes [u8],
    ) -> Result<Vec<&'bytes [u8]>> {
        elf.iter_note_sections(bytes, Some(NOTE_GNU_PROPERTY_SECTION_NAME))
            .into_iter()
            .chain(elf.iter_note_headers(bytes))
            .flatten()
            .try_fold(Vec::with_capacity(2), |mut elements, note| {
                let note = note.map_err(|source| Error::ParseFile { source })?;
                if note.name == ELF_NOTE_GNU
                    && note.n_type == goblin::elf::note::NT_GNU_PROPERTY_TYPE_0
                {
                    elements.push(note.desc);
                }
                Ok(elements)
            })
            .map(|mut elements| {
                elements.sort_unstable();
                elements.dedup();
                elements
            })
    }

    fn as_flags(&self, elf: &'bytes goblin::elf::Elf) -> Option<u64> {
        if let Ok(pr_data) = <[u8; 8]>::try_from(self.pr_data) {
            if elf.little_endian {
                Some(u64::from_le_bytes(pr_data))
            } else {
                Some(u64::from_be_bytes(pr_data))
            }
        } else if let Ok(pr_data) = <[u8; 4]>::try_from(self.pr_data) {
            if elf.little_endian {
                Some(u32::from_le_bytes(pr_data).into())
            } else {
                Some(u32::from_be_bytes(pr_data).into())
            }
        } else if let Ok(pr_data) = <[u8; 2]>::try_from(self.pr_data) {
            if elf.little_endian {
                Some(u16::from_le_bytes(pr_data).into())
            } else {
                Some(u16::from_be_bytes(pr_data).into())
            }
        } else if let Ok(pr_data) = <[u8; 1]>::try_from(self.pr_data) {
            Some(pr_data[0].into())
        } else {
            None
        }
    }
}

bitflags::bitflags! {
    #[derive(Debug)]
    pub(crate) struct GNUPropertyX86Feature1: u64 {
        /// Indirect Branch Tracking, a.k.a., Branch Target Identification.
        const IBT = 1 << 0;
        /// Shadow Stack.
        const SHSTK = 1 << 1;
    }
}

bitflags::bitflags! {
    #[derive(Debug)]
    pub(crate) struct GNUPropertyX86Feature2: u64 {
        const X86 = 1 << 0;
        const X87 = 1 << 1;
        const MMX = 1 << 2;
        const XMM = 1 << 3;
        const YMM = 1 << 4;
        const ZMM = 1 << 5;
        const FXSR = 1 << 6;
        const XSAVE = 1 << 7;
        const XSAVEOPT = 1 << 8;
        const XSAVEC = 1 << 9;
        const TMM = 1 << 10;
        const MASK = 1 << 11;
    }
}

bitflags::bitflags! {
    #[derive(Debug)]
    pub(crate) struct GNUPropertyX86ISA1: u64 {
        const BASELINE = 1 << 0;
        const V2 = 1 << 1;
        const V3 = 1 << 2;
        const V4 = 1 << 3;
    }
}

bitflags::bitflags! {
    #[derive(Debug)]
    pub(crate) struct GNUPropertyAArch64Feature1: u64 {
        /// Branch Target Identification, a.k.a., Indirect Branch Tracking.
        const BTI = 1 << 0;
        /// Pointer Authentication.
        const PAC = 1 << 1;
        /// Guarded Control Stack.
        const GCS = 1 << 2;
    }
}

bitflags::bitflags! {
    #[derive(Debug)]
    pub(crate) struct GNUPropertyAArch64FeaturePAuth: u64 {
        const INVALID = 0x0;
        const BAREMETAL = 0x1;
        const LLVM_LINUX = 0x1000_0002;
    }
}

#[derive(Debug)]
#[allow(unused)]
pub(crate) enum GNUProperty<'bytes> {
    StackSize(u64),
    NoCopyOnProtected,
    ProcessorSpecific(RawGNUProperty<'bytes>),
    UserSpecific(RawGNUProperty<'bytes>),
    X86Feature1And(GNUPropertyX86Feature1),
    X86UInt32And(u64),
    X86Feature2Used(GNUPropertyX86Feature2),
    X86UInt32OrAnd(u64),
    X86Feature2Needed(GNUPropertyX86Feature2),
    X86UInt32Or(u64),
    X86ISA1Used(GNUPropertyX86ISA1),
    X86ISA1Needed(GNUPropertyX86ISA1),
    AARCH64Feature1And(GNUPropertyAArch64Feature1),
    AARCH64FeaturePAuth(GNUPropertyAArch64FeaturePAuth),
}

const GNU_PROPERTY_STACK_SIZE: u32 = 1;
const GNU_PROPERTY_NO_COPY_ON_PROTECTED: u32 = 2;
const GNU_PROPERTY_LOPROC: u32 = 0xc000_0000;
const GNU_PROPERTY_HIPROC: u32 = 0xdfff_ffff;
const GNU_PROPERTY_LOUSER: u32 = 0xe000_0000;
const GNU_PROPERTY_HIUSER: u32 = 0xffff_ffff;

const GNU_PROPERTY_X86_UINT32_AND_LO: u32 = 0xc000_0002;
const GNU_PROPERTY_X86_FEATURE_1_AND: u32 = GNU_PROPERTY_X86_UINT32_AND_LO;
const GNU_PROPERTY_X86_UINT32_AND_HI: u32 = 0xc000_7fff;

const GNU_PROPERTY_X86_UINT32_OR_LO: u32 = 0xc000_8000;
const GNU_PROPERTY_X86_FEATURE_2_NEEDED: u32 = GNU_PROPERTY_X86_UINT32_OR_LO + 1;
const GNU_PROPERTY_X86_ISA_1_NEEDED: u32 = GNU_PROPERTY_X86_UINT32_OR_LO + 2;
const GNU_PROPERTY_X86_UINT32_OR_HI: u32 = 0xc000_ffff;

const GNU_PROPERTY_X86_UINT32_OR_AND_LO: u32 = 0xc001_0000;
const GNU_PROPERTY_X86_FEATURE_2_USED: u32 = GNU_PROPERTY_X86_UINT32_OR_AND_LO + 1;
const GNU_PROPERTY_X86_ISA_1_USED: u32 = GNU_PROPERTY_X86_UINT32_OR_AND_LO + 2;
const GNU_PROPERTY_X86_UINT32_OR_AND_HI: u32 = 0xc001_7fff;

const GNU_PROPERTY_AARCH64_FEATURE_1_AND: u32 = 0xc000_0000;
const GNU_PROPERTY_AARCH64_FEATURE_PAUTH: u32 = 0xc000_0001;

impl<'bytes> GNUProperty<'bytes> {
    pub(crate) fn parse_all(
        elf: &'bytes goblin::elf::Elf,
        bytes: &'bytes [u8],
    ) -> Result<Vec<Self>> {
        RawGNUProperty::parse_all(elf, bytes)?
            .into_iter()
            .map(|raw| Self::from_raw(&raw, elf))
            .collect()
    }

    fn from_raw(raw: &'_ RawGNUProperty<'bytes>, elf: &'bytes goblin::elf::Elf) -> Result<Self> {
        use goblin::elf::header::{EM_386, EM_AARCH64, EM_X86_64};

        match raw.pr_type {
            0 | 3..GNU_PROPERTY_LOPROC => Err(Error::InvalidGNUProgramPropertyType(raw.pr_type)),

            GNU_PROPERTY_STACK_SIZE => {
                let size = match (elf.is_64, elf.little_endian) {
                    (false, false) => {
                        u64::from(u32::from_be_bytes(<[u8; 4]>::try_from(raw.pr_data)?))
                    }
                    (false, true) => {
                        u64::from(u32::from_le_bytes(<[u8; 4]>::try_from(raw.pr_data)?))
                    }
                    (true, false) => u64::from_be_bytes(<[u8; 8]>::try_from(raw.pr_data)?),
                    (true, true) => u64::from_le_bytes(<[u8; 8]>::try_from(raw.pr_data)?),
                };

                Ok(Self::StackSize(size))
            }

            GNU_PROPERTY_NO_COPY_ON_PROTECTED => Ok(Self::NoCopyOnProtected),

            GNU_PROPERTY_LOPROC..=GNU_PROPERTY_HIPROC => {
                let value = match elf.header.e_machine {
                    EM_386 | EM_X86_64 => Self::from_raw_cpu_specific_x86(raw, elf),
                    EM_AARCH64 => Self::from_raw_cpu_specific_aarch64(raw, elf),
                    _ => None,
                };

                Ok(value.unwrap_or(Self::ProcessorSpecific(*raw)))
            }

            GNU_PROPERTY_LOUSER..=GNU_PROPERTY_HIUSER => Ok(Self::UserSpecific(*raw)),
        }
    }

    fn from_raw_cpu_specific_x86(
        raw: &'_ RawGNUProperty<'bytes>,
        elf: &'bytes goblin::elf::Elf,
    ) -> Option<Self> {
        const AFTER_GNU_PROPERTY_X86_UINT32_OR_AND_HI: u32 = GNU_PROPERTY_X86_UINT32_OR_AND_HI + 1;

        match raw.pr_type {
            0..GNU_PROPERTY_X86_UINT32_AND_LO | AFTER_GNU_PROPERTY_X86_UINT32_OR_AND_HI.. => None,

            GNU_PROPERTY_X86_UINT32_AND_LO..=GNU_PROPERTY_X86_UINT32_AND_HI => match raw.pr_type {
                0..GNU_PROPERTY_X86_FEATURE_1_AND | 0xc000_0003.. => {
                    raw.as_flags(elf).map(Self::X86UInt32And)
                }

                GNU_PROPERTY_X86_FEATURE_1_AND => raw
                    .as_flags(elf)
                    .map(GNUPropertyX86Feature1::from_bits_retain)
                    .map(Self::X86Feature1And),
            },

            GNU_PROPERTY_X86_UINT32_OR_LO..=GNU_PROPERTY_X86_UINT32_OR_HI => match raw.pr_type {
                0..GNU_PROPERTY_X86_FEATURE_2_NEEDED | 0xc000_8003.. => {
                    raw.as_flags(elf).map(Self::X86UInt32Or)
                }

                GNU_PROPERTY_X86_FEATURE_2_NEEDED => raw
                    .as_flags(elf)
                    .map(GNUPropertyX86Feature2::from_bits_retain)
                    .map(Self::X86Feature2Needed),

                GNU_PROPERTY_X86_ISA_1_NEEDED => raw
                    .as_flags(elf)
                    .map(GNUPropertyX86ISA1::from_bits_retain)
                    .map(Self::X86ISA1Needed),
            },

            GNU_PROPERTY_X86_UINT32_OR_AND_LO..=GNU_PROPERTY_X86_UINT32_OR_AND_HI => {
                match raw.pr_type {
                    0..GNU_PROPERTY_X86_FEATURE_2_USED | 0xc001_0003.. => {
                        raw.as_flags(elf).map(Self::X86UInt32OrAnd)
                    }

                    GNU_PROPERTY_X86_FEATURE_2_USED => raw
                        .as_flags(elf)
                        .map(GNUPropertyX86Feature2::from_bits_retain)
                        .map(Self::X86Feature2Used),

                    GNU_PROPERTY_X86_ISA_1_USED => raw
                        .as_flags(elf)
                        .map(GNUPropertyX86ISA1::from_bits_retain)
                        .map(Self::X86ISA1Used),
                }
            }
        }
    }

    fn from_raw_cpu_specific_aarch64(
        raw: &'_ RawGNUProperty<'bytes>,
        elf: &'bytes goblin::elf::Elf,
    ) -> Option<Self> {
        const AFTER_GNU_PROPERTY_AARCH64_FEATURE_PAUTH: u32 =
            GNU_PROPERTY_AARCH64_FEATURE_PAUTH + 1;

        match raw.pr_type {
            0..GNU_PROPERTY_AARCH64_FEATURE_1_AND | AFTER_GNU_PROPERTY_AARCH64_FEATURE_PAUTH.. => {
                None
            }

            GNU_PROPERTY_AARCH64_FEATURE_1_AND => raw
                .as_flags(elf)
                .map(GNUPropertyAArch64Feature1::from_bits_retain)
                .map(Self::AARCH64Feature1And),

            GNU_PROPERTY_AARCH64_FEATURE_PAUTH => raw
                .as_flags(elf)
                .map(GNUPropertyAArch64FeaturePAuth::from_bits_retain)
                .map(Self::AARCH64FeaturePAuth),
        }
    }
}