From: Michael Tremer Date: Thu, 2 Jan 2025 14:52:28 +0000 (+0000) Subject: ELF: Move the CF Protection check X-Git-Tag: 0.9.30~594 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5213e19184910214dcdcba94eeea30e2c448a8a3;p=pakfire.git ELF: Move the CF Protection check Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/elf.c b/src/libpakfire/elf.c index 268c2b3a5..c5f70e966 100644 --- a/src/libpakfire/elf.c +++ b/src/libpakfire/elf.c @@ -210,6 +210,14 @@ int pakfire_elf_type(struct pakfire_elf* self) { return self->ehdr.e_type; } +int pakfire_elf_machine(struct pakfire_elf* self) { + return self->ehdr.e_machine; +} + +int pakfire_elf_endianess(struct pakfire_elf* self) { + return self->ehdr.e_ident[EI_DATA]; +} + const char* pakfire_elf_build_id(struct pakfire_elf* self) { const void* buffer = NULL; ssize_t length; @@ -286,6 +294,41 @@ static int pakfire_elf_get_section(struct pakfire_elf* self, return 1; } +typedef int (*pakfire_elf_foreach_section_callback)(struct pakfire_elf* self, + const Elf_Scn* section, const GElf_Shdr* shdr, Elf_Data* data); + +static int pakfire_elf_foreach_section(struct pakfire_elf* self, + const Elf64_Word type, pakfire_elf_foreach_section_callback callback) { + Elf_Scn* section = NULL; + GElf_Shdr shdr; + Elf_Data* data; + int r = -EINVAL; + + // Walk through all sections + for (;;) { + section = elf_nextscn(self->elf, section); + if (!section) + break; + + // Fetch the section header + gelf_getshdr(section, &shdr); + + // Skip sections that don't match + if (type && shdr.sh_type != type) + continue; + + // Fetch the data + data = elf_getdata(section, NULL); + + // Call the callback + r = callback(self, section, &shdr, data); + if (r) + break; + } + + return r; +} + typedef int (*pakfire_elf_dyn_walk_callback) (struct pakfire_elf* self, const GElf_Shdr* shdr, const GElf_Dyn* dyn, void* data); @@ -480,6 +523,180 @@ int pakfire_elf_is_partially_relro(struct pakfire_elf* self) { return 0; } +static uint32_t read_4_bytes(const int endianess, const char* data) { + uint32_t bytes = 0; + + switch (endianess) { + // Litte Endian + case ELFDATA2LSB: + bytes = data[0] | + ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | + ((uint32_t)data[3] << 24); + break; + + // Big endian + case ELFDATA2MSB: + bytes = data[3] | + ((uint32_t)data[2] << 8) | + ((uint32_t)data[1] << 16) | + ((uint32_t)data[0] << 24); + break; + } + + return bytes; +} + +static int pakfire_elf_check_cf_protection_aarch64(struct pakfire_elf* self, + const int endianess, const uint32_t type, const char* payload) { + int flags = 0; + + switch (type) { + case GNU_PROPERTY_AARCH64_FEATURE_1_AND: + break; + + // Ignore the rest + default: + return 0; + } + + uint32_t property = read_4_bytes(endianess, payload); + + // Check for BTI + if (!(property & GNU_PROPERTY_AARCH64_FEATURE_1_BTI)) + flags |= PAKFIRE_ELF_MISSING_BTI; + + // Check for PAC + if (!(property & GNU_PROPERTY_AARCH64_FEATURE_1_PAC)) + flags |= PAKFIRE_ELF_MISSING_PAC; + + return 0; +} + +static int pakfire_elf_check_cf_protection_riscv64(struct pakfire_elf* self, + const int endianess, const uint32_t type, const char* payload) { + // There is nothing to do here + return 0; +} + +static int pakfire_elf_check_cf_protection_x86_64(struct pakfire_elf* self, + const int endianess, const uint32_t type, const char* payload) { + int flags = 0; + + switch (type) { + case GNU_PROPERTY_X86_FEATURE_1_AND: + break; + + // Ignore the rest + default: + return 0; + } + + uint32_t property = read_4_bytes(endianess, payload); + + // Check for IBT + if (!(property & GNU_PROPERTY_X86_FEATURE_1_IBT)) + flags |= PAKFIRE_ELF_MISSING_IBT; + + // Check for Shadow Stack + if (!(property & GNU_PROPERTY_X86_FEATURE_1_SHSTK)) + flags |= PAKFIRE_ELF_MISSING_SHSTK; + + return 0; +} + +static int pakfire_elf_check_cf_protection(struct pakfire_elf* self, + const Elf_Scn* section, const GElf_Shdr* shdr, Elf_Data* data) { + GElf_Nhdr nhdr = {}; + size_t offset = 0; + size_t offset_name = 0; + size_t offset_data = 0; + int r; + + // Fetch the .note header + offset = gelf_getnote(data, offset, &nhdr, &offset_name, &offset_data); + if (!offset) { + ERROR(self->ctx, "Could not read note section: %m\n"); + return -errno; + } + + // Skip any other note headers + switch (nhdr.n_type) { + case NT_GNU_PROPERTY_TYPE_0: + break; + + // We are not interested in this here + default: + return 0; + } + +#if 0 + // Check if the name starts with "GNU" + if (nhdr.n_namesz == sizeof(ELF_NOTE_GNU) { + if (strcmp(data->d_buf + offset_name, ELF_NOTE_GNU) == 0) + break; + } +#endif + + size_t length = nhdr.n_descsz; + + // XXX Check if this is aligned to 8 bytes or 4 bytes on 32 bit + + // Fetch the endianess + const int endianess = pakfire_elf_endianess(self); + + const char* payload = (const char*)data->d_buf + offset_data; + + while (length) { + // Read the type and size of the .note section + const uint32_t type = read_4_bytes(endianess, payload); + const uint32_t size = read_4_bytes(endianess, payload + 4); + + // Skip header + length -= sizeof(type) + sizeof(size); + payload += sizeof(type) + sizeof(size); + + if (length < size) { + ERROR(self->ctx, "GNU Property note has an incorrect format\n"); + return -EINVAL; + } + + switch (pakfire_elf_machine(self)) { + case EM_AARCH64: + r = pakfire_elf_check_cf_protection_aarch64(self, endianess, type, payload); + if (r < 0) + return r; + break; + + case EM_RISCV: + r = pakfire_elf_check_cf_protection_riscv64(self, endianess, type, payload); + if (r < 0) + return r; + break; + + case EM_X86_64: + r = pakfire_elf_check_cf_protection_x86_64(self, endianess, type, payload); + if (r < 0) + return r; + break; + + default: + ERROR(self->ctx, "Unsupported ELF type (%d)\n", pakfire_elf_machine(self)); + return -ENOTSUP; + } + + // Skip data part + length -= (size + 7) & ~7; + payload += (size + 7) & ~7; + } + + return 0; +} + +int pakfire_elf_has_cf_protection(struct pakfire_elf* self) { + return pakfire_elf_foreach_section(self, SHT_NOTE, pakfire_elf_check_cf_protection); +} + int pakfire_elf_is_stripped(struct pakfire_elf* self) { Elf_Scn* symtab = NULL; diff --git a/src/libpakfire/include/pakfire/elf.h b/src/libpakfire/include/pakfire/elf.h index d36f52b48..9f6261e21 100644 --- a/src/libpakfire/include/pakfire/elf.h +++ b/src/libpakfire/include/pakfire/elf.h @@ -39,6 +39,8 @@ struct pakfire_elf* pakfire_elf_unref(struct pakfire_elf* self); const char* pakfire_elf_path(struct pakfire_elf* self); int pakfire_elf_type(struct pakfire_elf* self); +int pakfire_elf_machine(struct pakfire_elf* self); +int pakfire_elf_endianess(struct pakfire_elf* self); const char* pakfire_elf_build_id(struct pakfire_elf* self); const char* pakfire_elf_debuglink(struct pakfire_elf* self); @@ -47,8 +49,17 @@ int pakfire_elf_has_ssp(struct pakfire_elf* self); int pakfire_elf_has_execstack(struct pakfire_elf* self); int pakfire_elf_is_fully_relro(struct pakfire_elf* self); int pakfire_elf_is_partially_relro(struct pakfire_elf* self); +int pakfire_elf_has_cf_protection(struct pakfire_elf* self); int pakfire_elf_is_stripped(struct pakfire_elf* self); +// Return bitmap for pakfire_elf_has_cf_protection() +enum { + PAKFIRE_ELF_MISSING_BTI = (1 << 0), + PAKFIRE_ELF_MISSING_PAC = (1 << 1), + PAKFIRE_ELF_MISSING_IBT = (1 << 2), + PAKFIRE_ELF_MISSING_SHSTK = (1 << 3), +}; + #endif /* PAKFIRE_PRIVATE */ #endif /* PAKFIRE_ELF_H */ diff --git a/src/libpakfire/linter-file.c b/src/libpakfire/linter-file.c index fb2f7ace5..f8ff6a501 100644 --- a/src/libpakfire/linter-file.c +++ b/src/libpakfire/linter-file.c @@ -346,42 +346,6 @@ static int pakfire_linter_check_script_interpreter(struct pakfire_linter_file* l return 0; } -typedef int (*__pakfire_linter_file_for_elf_section_callback)(struct pakfire_linter_file* lfile, - const Elf_Scn* section, const GElf_Shdr* shdr, Elf_Data* data); - -static int pakfire_linter_file_for_elf_section(struct pakfire_linter_file* lfile, - const Elf64_Word type, __pakfire_linter_file_for_elf_section_callback callback) { - Elf_Scn* section = NULL; - GElf_Shdr shdr; - Elf_Data* data; - int r = -EINVAL; - - // Walk through all sections - for (;;) { - section = elf_nextscn(lfile->elf, section); - if (!section) - break; - - // Fetch the section header - gelf_getshdr(section, &shdr); - - // Skip sections that don't match - if (type && shdr.sh_type != type) - continue; - - // Fetch the data - data = elf_getdata(section, NULL); - - // Call the callback - r = callback(lfile, section, &shdr, data); - if (r) - break; - } - - return r; -} - - static int pakfire_linter_file_get_elf_section(struct pakfire_linter_file* lfile, const Elf64_Word type, Elf_Scn** section, GElf_Shdr* header, Elf_Data** data) { Elf_Scn* s = NULL; @@ -571,186 +535,43 @@ static int pakfire_linter_file_check_runpath(struct pakfire_linter_file* lfile) lfile, __pakfire_linter_file_process_runpath, NULL); } -static uint32_t read_4_bytes(const int endianess, const char* data) { - uint32_t bytes = 0; - - switch (endianess) { - // Litte Endian - case ELFDATA2LSB: - bytes = data[0] | - ((uint32_t)data[1] << 8) | - ((uint32_t)data[2] << 16) | - ((uint32_t)data[3] << 24); - break; - - // Big endian - case ELFDATA2MSB: - bytes = data[3] | - ((uint32_t)data[2] << 8) | - ((uint32_t)data[1] << 16) | - ((uint32_t)data[0] << 24); - break; - } - - return bytes; -} - -static int __pakfire_linter_file_check_cf_protection_aarch64(struct pakfire_linter_file* lfile, - const int endianess, const uint32_t type, const char* payload) { - switch (type) { - case GNU_PROPERTY_AARCH64_FEATURE_1_AND: - break; - - // Ignore the rest - default: - return 0; - } - - uint32_t property = read_4_bytes(endianess, payload); - - // Check for BTI - if (!(property & GNU_PROPERTY_AARCH64_FEATURE_1_BTI)) - return pakfire_linter_file_error(lfile, "Branch Target Identification (BTI) is not enabled"); - - // Check for PAC - if (!(property & GNU_PROPERTY_AARCH64_FEATURE_1_PAC)) - return pakfire_linter_file_error(lfile, "Pointer Authentication (PAC) is not enabled"); - - return 0; -} - -static int __pakfire_linter_file_check_cf_protection_riscv64(struct pakfire_linter_file* lfile, - const int endianess, const uint32_t type, const char* payload) { - // There is nothing to do here - return 0; -} - -static int __pakfire_linter_file_check_cf_protection_x86(struct pakfire_linter_file* lfile, - const int endianess, const uint32_t type, const char* payload) { - switch (type) { - case GNU_PROPERTY_X86_FEATURE_1_AND: - break; - - // Ignore the rest - default: - return 0; - } - - uint32_t property = read_4_bytes(endianess, payload); - - // Check for IBT - if (!(property & GNU_PROPERTY_X86_FEATURE_1_IBT)) - return pakfire_linter_file_error(lfile, "Indirect Branch Tracking (IBT) is not enabled"); - - // Check for Shadow Stack - if (!(property & GNU_PROPERTY_X86_FEATURE_1_SHSTK)) - return pakfire_linter_file_error(lfile, "Shadow Stack is not enabled"); - - return 0; -} - -static int pakfire_linter_file_check_cf_protection_callback(struct pakfire_linter_file* lfile, - const Elf_Scn* section, const GElf_Shdr* shdr, Elf_Data* data) { - GElf_Ehdr ehdr = {}; - GElf_Nhdr nhdr = {}; - size_t offset = 0; - size_t offset_name = 0; - size_t offset_data = 0; +static int pakfire_linter_file_check_cf_protection(struct pakfire_linter_file* lfile) { int r; - // Fetch the ELF header - if (!gelf_getehdr(lfile->elf, &ehdr)) { - ERROR(lfile->ctx, "Could not fetch the ELF header for %s: %m\n", lfile->path); - return -errno; - } - - // Fetch the endianess - const int endianess = ehdr.e_ident[EI_DATA]; + // Fetch if CF Protection has been enabled + int flags = pakfire_elf_has_cf_protection(lfile->_elf); - // Fetch the .note header - offset = gelf_getnote(data, offset, &nhdr, &offset_name, &offset_data); - if (!offset) { - ERROR(lfile->ctx, "Could not read note section: %m\n"); - return -errno; + // aarch64: Branch Target Identification + if (flags & PAKFIRE_ELF_MISSING_BTI) { + r = pakfire_linter_file_error(lfile, "Branch Target Identification (BTI) is not enabled"); + if (r < 0) + return r; } - switch (nhdr.n_type) { - case NT_GNU_PROPERTY_TYPE_0: - break; - - // We are not interested in this here - default: - return 0; + // aarch64: Pointer Authentication + if (flags & PAKFIRE_ELF_MISSING_PAC) { + r = pakfire_linter_file_error(lfile, "Pointer Authentication (PAC) is not enabled"); + if (r < 0) + return r; } -#if 0 - // Check if the name starts with "GNU" - if (nhdr.n_namesz == sizeof(ELF_NOTE_GNU) { - if (strcmp(data->d_buf + offset_name, ELF_NOTE_GNU) == 0) - break; + // x86_64: Indirect Branch Tracking + if (flags & PAKFIRE_ELF_MISSING_IBT) { + r = pakfire_linter_file_error(lfile, "Indirect Branch Tracking (IBT) is not enabled"); + if (r < 0) + return r; } -#endif - - size_t length = nhdr.n_descsz; - - // XXX Check if this is aligned to 8 bytes or 4 bytes on 32 bit - - const char* payload = (const char*)data->d_buf + offset_data; - while (length) { - // Read the type and size of the .note section - const uint32_t type = read_4_bytes(endianess, payload); - const uint32_t size = read_4_bytes(endianess, payload + 4); - - // Skip header - length -= sizeof(type) + sizeof(size); - payload += sizeof(type) + sizeof(size); - - if (length < size) { - ERROR(lfile->ctx, "GNU Property note has an incorrect format\n"); - return -EINVAL; - } - - switch (ehdr.e_machine) { - case EM_AARCH64: - r = __pakfire_linter_file_check_cf_protection_aarch64( - lfile, endianess, type, payload); - if (r < 0) - return r; - break; - - case EM_RISCV: - r = __pakfire_linter_file_check_cf_protection_riscv64( - lfile, endianess, type, payload); - if (r < 0) - return r; - break; - - case EM_X86_64: - r = __pakfire_linter_file_check_cf_protection_x86( - lfile, endianess, type, payload); - if (r < 0) - return r; - break; - - default: - ERROR(lfile->ctx, "Unsupported ELF type (%d)\n", ehdr.e_machine); - return -ENOTSUP; - } - - // Skip data part - length -= (size + 7) & ~7; - payload += (size + 7) & ~7; + // x86_64: Shadow Stack + if (flags & PAKFIRE_ELF_MISSING_SHSTK) { + r = pakfire_linter_file_error(lfile, "Shadow Stack is not enabled"); + if (r < 0) + return r; } return 0; } -static int pakfire_linter_file_check_cf_protection(struct pakfire_linter_file* lfile) { - return pakfire_linter_file_for_elf_section( - lfile, SHT_NOTE, pakfire_linter_file_check_cf_protection_callback); -} - static int pakfire_linter_file_is_stripped(struct pakfire_linter_file* lfile) { switch (pakfire_elf_type(lfile->_elf)) { // Do not check Relocatable Objects