/* Detects the presence of retpoline instruction sequences in a binary.
Copyright (c) 2018 - 2019 Red Hat.
This is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3, or (at your
option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING3. If not,
see <http://www.gnu.org/licenses/>.
It is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. */
#include "annobin-global.h"
#include "annocheck.h"
static bool disabled = false;
static bool detect_retpolines = true;
static bool detect_ibt_plt_stubs = false;
static bool found_retpolines = false;
static int e_type;
static enum
{
NOT_FOUND,
FOUND,
NEEDED
} found_ibt_plt_stubs;
static bool
start_file (annocheck_data * data)
{
int e_machine;
if (detect_retpolines == false && detect_ibt_plt_stubs == false)
disabled = true;
if (disabled)
return false;
found_retpolines = false;
found_ibt_plt_stubs = NOT_FOUND;
if (data->is_32bit)
{
Elf32_Ehdr * hdr = elf32_getehdr (data->elf);
e_type = hdr->e_type;
e_machine = hdr->e_machine;
}
else
{
Elf64_Ehdr * hdr = elf64_getehdr (data->elf);
e_type = hdr->e_type;
e_machine = hdr->e_machine;
}
return (e_machine == EM_X86_64);
}
static bool
interesting_sec (annocheck_data * data,
annocheck_section * sec)
{
if (disabled)
return false;
/* For retpolines we want to scan code sections. */
if (detect_retpolines
&& sec->shdr.sh_type == SHT_PROGBITS
&& sec->shdr.sh_flags & SHF_EXECINSTR
&& sec->shdr.sh_size > 0)
return true;
if (detect_ibt_plt_stubs)
{
/* For ibt stubs we want to scan the .plt section. */
if (streq (sec->secname, ".plt"))
return true;
/* We also want to check the GNU Property note. */
if (sec->shdr.sh_type == SHT_NOTE)
return true;
}
/* We do not need any more information from the section,
so there is no need to run the checker. */
return false;
}
static bool
walk_property_notes (annocheck_data * data,
annocheck_section * sec,
GElf_Nhdr * note,
size_t name_offset,
size_t data_offset,
void * ptr)
{
if (note->n_type == NT_GNU_PROPERTY_TYPE_0
&& found_ibt_plt_stubs != FOUND
&& (e_type == ET_EXEC || e_type == ET_DYN))
/* FIXME: We are assuming that if a property note is present then CET should be enabled. */
found_ibt_plt_stubs = NEEDED;
return true;
}
static bool
check_sec (annocheck_data * data,
annocheck_section * sec)
{
if (sec->data->d_size == 0)
return true;
if (detect_retpolines
&& ! found_retpolines
&& sec->shdr.sh_type == SHT_PROGBITS
&& sec->shdr.sh_flags & SHF_EXECINSTR
&& sec->shdr.sh_size > 0)
{
/* Look for the binary sequence:
f3 90 pause
0f ae e8 lfence. */
#define SEQ_LENGTH 5
static char sequence[SEQ_LENGTH] = { 0xf3, 0x90, 0x0f, 0xae, 0xe8 };
size_t i;
einfo (VERBOSE2, "%s: check contents of %s section", data->filename, sec->secname);
for (i = 0; i < sec->data->d_size - SEQ_LENGTH; i++)
{
/* FIXME: There are faster ways of doing this... */
if (memcmp (sec->data->d_buf + i, sequence, SEQ_LENGTH) == 0)
{
einfo (VERBOSE2, "%s: sequence found in section %s", data->filename, sec->secname);
found_retpolines = true;
break;
}
}
}
if (detect_ibt_plt_stubs
&& found_ibt_plt_stubs == NOT_FOUND
&& streq (sec->secname, ".plt"))
{
const unsigned char * buf = sec->data->d_buf;
einfo (VERBOSE, "%s: check contents of .plt section", data->filename);
/* Look for the ENDBR64 insn in the sequence:
10: f3 0f 1e fa endbr64
14: ff 35 8e 0f 20 00 pushq 0x......(%rip)
1a: ff 25 78 0f 20 00 jmpq *0x......(%rip). */
if (sec->data->d_size >= 0x20
&& buf[0x10] == 0xf3
&& buf[0x11] == 0x0f
&& buf[0x12] == 0x1e
&& buf[0x13] == 0xfa)
found_ibt_plt_stubs = FOUND;
}
if (detect_ibt_plt_stubs
&& sec->shdr.sh_type == SHT_NOTE
&& streq (sec->secname, ".note.gnu.property"))
{
einfo (VERBOSE, "%s: scan GNU property notes", data->filename);
return annocheck_walk_notes (data, sec, walk_property_notes, NULL);
}
return true;
}
static void
usage (void)
{
einfo (INFO, "Detects the presence of specific code sequences in binary files");
einfo (INFO, " Use --[no-]detect-retpolines to enable detection of retpolines. [default: enabled]");
einfo (INFO, " Use --[no-]detect-ibt-plt-stubs to enable detection of IBT enabled PLT stubs. [default: disabled]");
}
static bool
process_arg (const char * arg, const char ** argv, const uint argc, uint * next_indx)
{
if (streq (arg, "--detect-retpolines"))
detect_retpolines = true;
else if (streq (arg, "--no-detect-retpolines"))
detect_retpolines = false;
else if (streq (arg, "--detect-ibt-plt-stubs"))
detect_ibt_plt_stubs = true;
else if (streq (arg, "--no-detect-ibt-plt-stubs"))
detect_ibt_plt_stubs = false;
else
return false;
return true;
}
static void
version (void)
{
einfo (INFO, "Version 1.1");
}
static bool
end_file (annocheck_data * data)
{
if (disabled)
return false;
if (detect_retpolines)
einfo (VERBOSE, "%s: %s", data->filename, found_retpolines ? "uses retpolines" : "does not use retpolines");
if (detect_ibt_plt_stubs)
{
switch (found_ibt_plt_stubs)
{
case NOT_FOUND:
einfo (INFO, "%s: no IBT enabled stubs found (not needed)", data->filename);
break;
case FOUND:
einfo (INFO, "%s: IBT enabled stubs found", data->filename);
break;
case NEEDED:
einfo (INFO, "%s: no IBT enabled stubs found (but they are needed)", data->filename);
return false;
break;
}
}
return true;
}
struct checker retpoline_checker =
{
"Retpoline Detector",
start_file,
interesting_sec,
check_sec,
NULL, /* interesting_seg */
NULL, /* check_seg */
end_file,
process_arg, /* process_arg */
usage,
version,
NULL, /* start_scan */
NULL, /* end_scan */
NULL /* internal */
};
static __attribute__((constructor)) void
register_checker (void)
{
if (! annocheck_add_checker (& retpoline_checker, ANNOBIN_VERSION / 100))
disabled = true;
}