master ohmu/fastfetch-openbsd / src / common / impl / binary_linux.c
ohmu  ·  2026-10-15
  1#include "common/binary.h"
  2#include "common/debug.h"
  3
  4#if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__)
  5
  6    #include "common/io.h"
  7    #include "common/library.h"
  8    #include "common/strutil.h"
  9
 10    #include <libelf.h> // #1254
 11    #include <fcntl.h>
 12
 13/**
 14 * Structure to hold dynamically loaded libelf function pointers
 15 */
 16struct FFElfData {
 17    FF_LIBRARY_SYMBOL(elf_version)
 18    FF_LIBRARY_SYMBOL(elf_begin)
 19    FF_LIBRARY_SYMBOL(elf_getshdrstrndx)
 20    FF_LIBRARY_SYMBOL(elf_nextscn)
 21    FF_LIBRARY_SYMBOL(elf64_getshdr)
 22    FF_LIBRARY_SYMBOL(elf32_getshdr)
 23    FF_LIBRARY_SYMBOL(elf_getdata)
 24    FF_LIBRARY_SYMBOL(elf_strptr)
 25    FF_LIBRARY_SYMBOL(elf_end)
 26
 27    bool inited;
 28} elfData;
 29
 30/**
 31 * Extracts string literals from an ELF (Linux/Unix) binary file
 32 *
 33 * This function loads the libelf library dynamically, opens the ELF file,
 34 * locates the .rodata section (which contains string literals), and
 35 * scans it for valid strings. Each string found is passed to the
 36 * callback function for processing.
 37 *
 38 * The function supports both 32-bit and 64-bit ELF formats.
 39 */
 40const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
 41    // Initialize libelf if not already done
 42    if (!elfData.inited) {
 43        elfData.inited = true;
 44        FF_LIBRARY_LOAD_MESSAGE(libelf, "libelf" FF_LIBRARY_EXTENSION, 1);
 45        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version)
 46        if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) {
 47            FF_DEBUG("elf_version() failed");
 48            return "elf_version() failed";
 49        }
 50
 51        // Load all required libelf functions
 52        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_begin)
 53        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getshdrstrndx)
 54        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_nextscn)
 55        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf64_getshdr)
 56        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf32_getshdr)
 57        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getdata)
 58        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_strptr)
 59        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_end)
 60
 61        libelf = nullptr;
 62    }
 63
 64    if (elfData.ffelf_end == nullptr) {
 65        FF_DEBUG("load libelf failed");
 66        return "load libelf failed";
 67    }
 68
 69    // Open the ELF file
 70    FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC);
 71    if (fd < 0) {
 72        FF_DEBUG("open(%s) failed: %s", elfFile, strerror(errno));
 73        return "open() failed";
 74    }
 75
 76    Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, nullptr);
 77    if (elf == nullptr) {
 78        FF_DEBUG("elf_begin() failed");
 79        return "elf_begin() failed";
 80    }
 81
 82    // Get the section header string table index
 83    size_t shstrndx = 0;
 84    if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) {
 85        FF_DEBUG("elf_getshdrstrndx() failed");
 86        elfData.ffelf_end(elf);
 87        return "elf_getshdrstrndx() failed";
 88    }
 89
 90    // Iterate through all sections, looking for .rodata which contains string literals
 91    Elf_Scn* scn = nullptr;
 92    while ((scn = elfData.ffelf_nextscn(elf, scn)) != nullptr) {
 93        // Try 64-bit section header first, then 32-bit if that fails
 94        Elf64_Shdr* shdr64 = elfData.ffelf64_getshdr(scn);
 95        Elf32_Shdr* shdr32 = nullptr;
 96        if (shdr64 == nullptr) {
 97            shdr32 = elfData.ffelf32_getshdr(scn);
 98            if (shdr32 == nullptr) {
 99                continue;
100            }
101        }
102
103        // Get the section name and check if it's .rodata
104        const char* name = elfData.ffelf_strptr(elf, shstrndx, shdr64 ? shdr64->sh_name : shdr32->sh_name);
105        if (name == nullptr || !ffStrEquals(name, ".rodata")) {
106            continue;
107        }
108
109        // Get the section data
110        Elf_Data* data = elfData.ffelf_getdata(scn, nullptr);
111        if (data == nullptr) {
112            continue;
113        }
114
115        // Scan the section for string literals
116        for (size_t off = 0; off < data->d_size; ++off) {
117            const char* p = (const char*) data->d_buf + off;
118            if (*p == '\0') {
119                continue;
120            }
121            uint32_t len = (uint32_t) strnlen(p, data->d_size - off);
122            if (len < minLength) {
123                off += len;
124                continue;
125            }
126            // Only process printable ASCII characters
127            if (*p >= ' ' && *p <= '~') // Ignore control characters
128            {
129                if (!cb(p, len, userdata)) {
130                    break;
131                }
132            }
133            off += len;
134        }
135
136        break;
137    }
138
139    elfData.ffelf_end(elf);
140    return nullptr;
141}
142
143#else
144
145/**
146 * Fallback implementation when libelf is not available
147 */
148const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
149    FF_UNUSED(file, cb, userdata, minLength);
150    FF_DEBUG("Fastfetch was built without libelf support");
151    return "Fastfetch was built without libelf support";
152}
153
154#endif