From 284632492ae6de5bec94b4720208d3ce092d0f43 Mon Sep 17 00:00:00 2001 From: Nick Clifton Date: Wed, 6 Jun 2018 12:36:51 +0100 Subject: [PATCH] Import eu-checksec program into annobin. Rename to annocheck. Add documentation. Update testsuite to use it. Fix minor bugs exposed by using annocheck. Fix exit codes for scripts. --- Makefile.am | 2 +- Makefile.in | 2 +- annocheck/Makefile.am | 12 + annocheck/Makefile.in | 521 ++++++++++++++ annocheck/annocheck.c | 1242 ++++++++++++++++++++++++++++++++++ annocheck/annocheck.h | 224 ++++++ annocheck/built-by.c | 246 +++++++ annocheck/hardened.c | 1061 +++++++++++++++++++++++++++++ configure | 25 +- configure.ac | 4 +- doc/Makefile.am | 9 +- doc/Makefile.in | 9 +- doc/annobin.1 | 14 +- doc/annobin.info | 274 ++++++-- doc/annobin.texi | 234 ++++++- doc/annocheck.1 | 316 +++++++++ doc/built-by.1 | 2 +- doc/check-abi.1 | 2 +- doc/hardened.1 | 2 +- doc/run-on-binaries.1 | 2 +- plugin/annobin.cc | 21 +- scripts/check-abi | 37 +- scripts/hardened | 49 +- tests/assembler-gap-test | 1 - tests/function-sections-test | 6 + tests/hardening-fail-test | 2 +- tests/hardening-test | 21 +- tests/hello_hard.c | 30 + 28 files changed, 4211 insertions(+), 159 deletions(-) create mode 100644 annocheck/Makefile.am create mode 100644 annocheck/Makefile.in create mode 100644 annocheck/annocheck.c create mode 100644 annocheck/annocheck.h create mode 100644 annocheck/built-by.c create mode 100644 annocheck/hardened.c create mode 100644 doc/annocheck.1 create mode 100644 tests/hello_hard.c diff --git a/Makefile.am b/Makefile.am index 73d422f..5531465 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,6 +6,6 @@ ## Process this file with automake to produce Makefile.in. -SUBDIRS = plugin scripts doc tests +SUBDIRS = plugin scripts doc tests annocheck dist_doc_DATA = LICENSE COPYING3 ACLOCAL_AMFLAGS = -I config diff --git a/Makefile.in b/Makefile.in index 1e3bfb6..fc49f2f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -256,7 +256,7 @@ target_vendor = @target_vendor@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -SUBDIRS = plugin scripts doc tests +SUBDIRS = plugin scripts doc tests annocheck dist_doc_DATA = LICENSE COPYING3 ACLOCAL_AMFLAGS = -I config all: all-recursive diff --git a/annocheck/Makefile.am b/annocheck/Makefile.am new file mode 100644 index 0000000..2dfceb3 --- /dev/null +++ b/annocheck/Makefile.am @@ -0,0 +1,12 @@ +# copyright : Copyright (c) 2017-2018 Red Hat +# license : GNU GPL v3; see accompanying LICENSE file + +AM_CPPFLAGS = -I'$(top_builddir)' -I'$(top_srcdir)' +AUTOMAKE_OPTIONS = no-dependencies + + +bin_PROGRAMS = annocheck +annocheck_SOURCES = annocheck.c hardened.c built-by.c annocheck.h +annocheck_LDADD = -lelf -ldw -lrpm -lrpmio -liberty + + diff --git a/annocheck/Makefile.in b/annocheck/Makefile.in new file mode 100644 index 0000000..4aff96e --- /dev/null +++ b/annocheck/Makefile.in @@ -0,0 +1,521 @@ +# Makefile.in generated by automake 1.11.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# copyright : Copyright (c) 2017-2018 Red Hat +# license : GNU GPL v3; see accompanying LICENSE file + +VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +bin_PROGRAMS = annocheck$(EXEEXT) +subdir = annocheck +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/config/acx.m4 \ + $(top_srcdir)/config/depstand.m4 \ + $(top_srcdir)/config/disable-rpath.m4 \ + $(top_srcdir)/config/gcc-plugin.m4 \ + $(top_srcdir)/config/lead-dot.m4 \ + $(top_srcdir)/config/libtool-link.m4 \ + $(top_srcdir)/config/libtool.m4 \ + $(top_srcdir)/config/lthostflags.m4 \ + $(top_srcdir)/config/ltoptions.m4 \ + $(top_srcdir)/config/ltsugar.m4 \ + $(top_srcdir)/config/ltversion.m4 \ + $(top_srcdir)/config/lt~obsolete.m4 \ + $(top_srcdir)/config/override.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/plugin/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_annocheck_OBJECTS = annocheck.$(OBJEXT) hardened.$(OBJEXT) \ + built-by.$(OBJEXT) +annocheck_OBJECTS = $(am_annocheck_OBJECTS) +annocheck_DEPENDENCIES = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/plugin +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(annocheck_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +ETAGS = etags +CTAGS = ctags +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_libsubdir = @build_libsubdir@ +build_os = @build_os@ +build_subdir = @build_subdir@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_subdir = @host_subdir@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_host_flags = @lt_host_flags@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_noncanonical = @target_noncanonical@ +target_os = @target_os@ +target_plugin = @target_plugin@ +target_subdir = @target_subdir@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = -I'$(top_builddir)' -I'$(top_srcdir)' +AUTOMAKE_OPTIONS = no-dependencies +annocheck_SOURCES = annocheck.c hardened.c built-by.c annocheck.h +annocheck_LDADD = -lelf -ldw -lrpm -lrpmio -liberty +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign annocheck/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign annocheck/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +annocheck$(EXEEXT): $(annocheck_OBJECTS) $(annocheck_DEPENDENCIES) $(EXTRA_annocheck_DEPENDENCIES) + @rm -f annocheck$(EXEEXT) + $(LINK) $(annocheck_OBJECTS) $(annocheck_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(COMPILE) -c $< + +.c.obj: + $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: + $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic clean-libtool ctags distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags dvi dvi-am \ + html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/annocheck/annocheck.c b/annocheck/annocheck.c new file mode 100644 index 0000000..302ea14 --- /dev/null +++ b/annocheck/annocheck.c @@ -0,0 +1,1242 @@ +/* annocheck - A tool for checking security features of binares. + Copyright (c) 2018 Red Hat. + Created by Nick Clifton. + + 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. + + 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 "annocheck.h" +#include +#include +#include +#include +#include + +/* Maximum number of input files. FIXME: Use a linked list instead. */ +#define MAX_NUM_FILES 256 + +/* -1: silent, 0: normal, 1: verbose, 2: very verbose. */ +ulong verbosity = 0; + +uint major_version = 1; +uint minor_version = 0; + +static ulong num_files = 0; +static const char * files[MAX_NUM_FILES]; +static const char * progname; +static const char * base_component = "annocheck"; +static const char * component = "annocheck"; +static bool ignore_unknown = false; +static char * saved_args = NULL; +static char * prefix = ""; +static const char * dwarf_path = NULL; + +static checker * first_checker = NULL; +static checker * first_sec_checker = NULL; +static checker * first_seg_checker = NULL; + +typedef struct checker_internal +{ + /* Pointer to the next section checker. */ + struct checker * next_sec; + + /* Pointer to the next segment checker. */ + struct checker * next_seg; + + /* Pointer to the next checker. */ + struct checker * next; + +} checker_internal; + +/* -------------------------------------------------------------------- */ +/* Print a message on stdout or stderr. Returns FALSE (for error + messages) so that it can be used as a terminator in boolean functions. */ + +bool +einfo (einfo_type type, const char * format, ...) +{ + FILE * file; + const char * do_newline = ""; + const char * pref = NULL; + va_list args; + bool res = false; + + switch (type) + { + case WARN: + case SYS_WARN: + pref = "Warning"; + file = stderr; + break; + case ERROR: + case SYS_ERROR: + pref = "Error"; + file = stderr; + break; + case FAIL: + pref = "Internal Failure"; + file = stderr; + break; + case VERBOSE2: + case VERBOSE: + //pref = "Verbose"; + file = stdout; + res = true; + break; + case INFO: + //pref = "Info"; + file = stdout; + res = true; + break; + case PARTIAL: + // pref = ""; + file = stdout; + res = true; + break; + default: + fprintf (stderr, "ICE: Unknown einfo type %x\n", type); + exit (-1); + } + + if (verbosity == -1UL + || (type == VERBOSE && verbosity < 1) + || (type == VERBOSE2 && verbosity < 2)) + return res; + + fflush (stderr); + fflush (stdout); + + if (type != PARTIAL) + fprintf (file, "%s: ", component); + + if (format[strlen (format) - 1] != '\n') + do_newline = ".\n"; + + if (pref) + fprintf (file, "%s: ", pref); + + if (prefix[0]) + fprintf (file, "%s ", prefix); + + va_start (args, format); + vfprintf (file, format, args); + va_end (args); + + if (type == SYS_WARN || type == SYS_ERROR) + fprintf (file, ": system error: %s", strerror (errno)); + + if (type != PARTIAL) + fprintf (file, "%s", do_newline); + return res; +} + +/* -------------------------------------------------------------------- */ + +static void +push_component (checker * checker) +{ + component = checker->name; +} + +static void +pop_component (void) +{ + component = base_component; +} + +/* -------------------------------------------------------------------- */ + +static void +add_file (const char * filename) +{ + if (num_files == MAX_NUM_FILES) + return; + + files[num_files ++] = filename; +} + +static void +print_version (void) +{ + einfo (INFO, "Version %d.%d", major_version, minor_version); + + checker * checker; + for (checker = first_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next) + if (checker->version) + { + push_component (checker); + checker->version (); + pop_component (); + } +} + +static void +usage (void) +{ + einfo (INFO, "Runs various scans on the given files"); + einfo (INFO, "Useage: %s [options] ", component); + einfo (INFO, " Options are:"); + einfo (INFO, " --dwarf-dir= [Look in for separate dwarf debug information files]"); + einfo (INFO, " --help [Display this message & exit]"); + einfo (INFO, " --ignore-unknown [Do not complain about unknown file types]"); + einfo (INFO, " --prefix= [Include in the output description]"); + einfo (INFO, " --quiet [Do not print anything, just return an exit status]"); + einfo (INFO, " --verbose [Produce informational messages whilst working. Repeat for more information]"); + einfo (INFO, " --version [Report the verion of the tool & exit]"); + + einfo (INFO, "The following scanning tools are available:"); + checker * checker; + for (checker = first_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next) + { + push_component (checker); + if (checker->usage) + checker->usage (); + else + einfo (INFO, "Does not have any specific options"); + pop_component (); + } +} + +static void +save_arg (const char * arg) +{ + if (saved_args) + { + char * new_saved_args = concat (saved_args, " ", arg, NULL); + free (saved_args); + saved_args = new_saved_args; + } + else + saved_args = concat (arg, NULL); +} + +/* Handle command line options. Returns to caller if there is + something to do. */ + +static bool +process_command_line (uint argc, const char * argv[]) +{ + uint a = 1; + + progname = argv[0]; + + while (a < argc) + { + const char * arg = argv[a]; + bool used = false; + checker * checker; + const char * parameter; + + ++ a; + + for (checker = first_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next) + if (checker->process_arg != NULL) + { + push_component (checker); + if (checker->process_arg (arg, argv, argc, & a)) + used = true; + pop_component (); + } + + if (used) + { + save_arg (arg); + continue; + } + + if (arg[0] == '-') + { + const char * orig_arg = arg; + + arg += (arg[1] == '-' ? 2 : 1); + switch (*arg) + { + case 'h': + usage (); + exit (EXIT_SUCCESS); + + case 'i': + ignore_unknown = true; + break; + + case 'q': + save_arg (orig_arg); + verbosity = -1UL; + break; + + case 'd': + save_arg (orig_arg); + parameter = strchr (arg, '='); + if (parameter == NULL) + parameter = argv[a++]; + else + parameter ++; + dwarf_path = parameter; + break; + + case 'p': + save_arg (orig_arg); + parameter = strchr (arg, '='); + if (parameter == NULL) + parameter = argv[a++]; + else + parameter ++; + /* Prefix arguments accumulate. */ + prefix = concat (prefix, parameter, NULL); + break; + + case 'v': + if (const_strneq (arg, "version")) + { + print_version (); + exit (EXIT_SUCCESS); + } + else if (const_strneq (arg, "verbose") + /* Allow -v as an alias for --verbose. */ + || arg[1] == 0) + { + save_arg (orig_arg); + verbosity ++; + } + else + goto unknown_arg; + break; + + default: + unknown_arg: + einfo (WARN, "Unrecognised command line option: %s ", orig_arg); + usage (); + return false; + } + } + else + add_file (arg); + } + + if (num_files == 0) + { + einfo (WARN, "No input files specified"); + usage (); + return false; + } + + return true; +} + +/* -------------------------------------------------------------------- */ + +/* Utility function to walk over a note section calling FUNC + on each note. PTR is passed to FUNC along with a pointer to the note. + If FUNC returns false the walk is terminated. + Returns FALSE if the walk could not be executed. */ + +bool +eu_checksec_walk_notes (eu_checksec_data * data, eu_checksec_section * sec, note_walker func, void * ptr) +{ + assert (data != NULL && sec != NULL && func != NULL); + + if (sec->shdr.sh_type != SHT_NOTE + || sec->data == NULL + || sec->data->d_size == 0) + return false; + + size_t offset = 0; + + GElf_Nhdr note; + size_t name_offset; + size_t data_offset; + + while ((offset = gelf_getnote (sec->data, offset, & note, & name_offset, & data_offset)) != 0) + if (! func (data, sec, & note, name_offset, data_offset, ptr)) + break; + + return true; +} + +/* Read in the section header for SECTION. */ + +static void +read_section_header (eu_checksec_data * data, Elf_Scn * section, Elf64_Shdr * s64hdr) +{ + if (data->is_32bit) + { + Elf32_Shdr * shdr = elf32_getshdr (section); + + s64hdr->sh_name = shdr->sh_name; + s64hdr->sh_type = shdr->sh_type; + s64hdr->sh_flags = shdr->sh_flags; + s64hdr->sh_addr = shdr->sh_addr; + s64hdr->sh_offset = shdr->sh_offset; + s64hdr->sh_size = shdr->sh_size; + s64hdr->sh_link = shdr->sh_link; + s64hdr->sh_info = shdr->sh_info; + s64hdr->sh_addralign = shdr->sh_addralign; + s64hdr->sh_entsize = shdr->sh_entsize; + } + else + memcpy (s64hdr, elf64_getshdr (section), sizeof * s64hdr); +} + +/* -------------------------------------------------------------------- */ + +static bool +run_checkers (const char * filename, int fd, Elf * elf) +{ + eu_checksec_data data; + + memset (& data, 0, sizeof data); + data.full_filename = filename; + data.filename = BE_VERBOSE ? filename : lbasename (filename); + data.fd = fd; + data.elf = elf; + data.is_32bit = gelf_getclass (elf) == ELFCLASS32; + + checker * checker; + + /* Call the checker start functions. */ + for (checker = first_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next) + if (checker->start) + { + push_component (checker); + checker->start (& data); + pop_component (); + } + + bool ret = true; + + if (first_sec_checker != NULL) + { + size_t shstrndx; + + if (elf_getshdrstrndx (elf, & shstrndx) < 0) + return einfo (ERROR, "%s: Unable to locate string section", filename); + + Elf_Scn * scn = NULL; + + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + eu_checksec_section sec; + + memset (& sec, 0, sizeof sec); + + sec.scn = scn; + read_section_header (& data, scn, & sec.shdr); + sec.secname = elf_strptr (elf, shstrndx, sec.shdr.sh_name); + + if (sec.shdr.sh_size == 0) + { + einfo (VERBOSE2, "%s: Skipping empty section: %s", filename, sec.secname); + continue; + } + + einfo (VERBOSE2, "%s: Examining section %s", filename, sec.secname); + + /* Walk the checkers, asking each in turn if they are interested in this section. */ + for (checker = first_sec_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next_sec) + { + if (checker->interesting_sec == NULL) + continue; + + push_component (checker); + + if (checker->interesting_sec (& data, & sec)) + { + /* Delay loading the section contents until a checker expresses interest. */ + if (sec.data == NULL) + { + sec.data = elf_getdata (scn, NULL); + if (sec.data == NULL) + ret = einfo (ERROR, "Failed to read in section %s", sec.secname); + } + + if (sec.data != NULL) + { + einfo (VERBOSE2, "is interested in section %s", sec.secname); + + ret &= checker->check_sec (& data, & sec); + } + } + else + einfo (VERBOSE2, "is not interested in %s", sec.secname); + + pop_component (); + } + } + } + + if (first_seg_checker != NULL) + { + size_t phnum; + + elf_getphdrnum (elf, & phnum); + + for (size_t cnt = 0; cnt < phnum; ++cnt) + { + GElf_Phdr mem; + eu_checksec_segment seg; + + memset (& seg, 0, sizeof seg); + + seg.phdr = gelf_getphdr (elf, cnt, & mem); + seg.number = cnt; + + einfo (VERBOSE2, "%s: considering segment %lu", filename, cnt); + + for (checker = first_seg_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next_seg) + { + if (checker->interesting_seg == NULL) + continue; + + push_component (checker); + + if (checker->interesting_seg (& data, & seg)) + { + /* Delay loading the contents of the segment until they are actually needed. */ + if (seg.data == NULL) + seg.data = elf_getdata_rawchunk (elf, seg.phdr->p_offset, + seg.phdr->p_filesz, ELF_T_BYTE); + + ret &= checker->check_seg (& data, & seg); + } + else + einfo (VERBOSE2, "is not interested in segment %lu", cnt); + + pop_component (); + } + } + } + + for (checker = first_checker; checker != NULL; checker = ((checker_internal *)(checker->internal))->next) + if (checker->finish) + { + push_component (checker); + ret &= checker->finish (& data); + pop_component (); + } + + return ret; +} + +static Dwarf * +follow_debuglink (eu_checksec_data * data, Dwarf * dwarf) +{ + char * canon_dir = NULL; + char * debugfile = NULL; + int fd; + + /* First try the build-id method. */ + ssize_t build_id_len; + const void * build_id_ptr; + + build_id_len = dwelf_elf_gnu_build_id (data->elf, & build_id_ptr); + if (build_id_len > 0) + { + /* Compute the path to the debuginfo from the build id. + Since we know that we are running on a Fedora/RHEL + system we can just check the standard Fedora location: + + /usr/lib/debug/.build-id/NN/NN+NN.debug + + where NNNN+NN is the build-id value as a hexadecimal + string. */ + + const char * prefix = "/usr/lib/debug/.build-id/"; + const char * suffix = ".debug"; + char * debugfile = xmalloc (strlen (prefix) + + build_id_len * 2 + + strlen (suffix) + 2); + char * n = debugfile; + unsigned char * d = (unsigned char *) build_id_ptr; + + n += sprintf (n, "%s%02x/", prefix, *d++); + build_id_len --; + while (build_id_len --) + n += sprintf (n, "%02x", *d++); + n += sprintf (n, suffix); + + einfo (VERBOSE, "%s: Look for build-id based debug info file: %s", + data->filename, debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + free (debugfile); + } + + /* Now try using a .gnu.debuglink section. */ + GElf_Word crc; + const char * link; + + if ((link = dwelf_elf_gnu_debuglink (data->elf, & crc)) == NULL) + return NULL; + + einfo (VERBOSE, "%s: Try to find separate debug file for: %s", data->filename, link); + + size_t canon_dirlen; + + /* Attempt to locate the separate file. */ + canon_dir = lrealpath (data->filename); + + for (canon_dirlen = strlen (canon_dir); canon_dirlen > 0; canon_dirlen--) + if (canon_dir[canon_dirlen - 1] == '/') + break; + canon_dir[canon_dirlen] = '\0'; + +#define DEBUGDIR_1 "/lib/debug" +#define DEBUGDIR_2 "/usr/lib/debug" +#define DEBUGDIR_3 "/usr/lib/debug/usr" +#define DEBUGDIR_4 "/usr/lib/debug/usr/lib64" + + debugfile = (char *) xmalloc (strlen (DEBUGDIR_1) + 1 + + strlen (DEBUGDIR_2) + + strlen (DEBUGDIR_3) + + strlen (DEBUGDIR_4) + + canon_dirlen + + strlen (".debug/") + + strlen (link) + + 1); + + /* If we have been provided with a dwarf directory, try that first. */ + if (dwarf_path) + { + sprintf (debugfile, "%s/%s", dwarf_path, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + } + + /* First try in the current directory. */ + sprintf (debugfile, "%s", link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Then try in a subdirectory called .debug. */ + sprintf (debugfile, ".debug/%s", link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Then try in the same directory as the original file. */ + sprintf (debugfile, "%s%s", canon_dir, link); + einfo (VERBOSE2, "try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* And the .debug subdirectory of that directory. */ + sprintf (debugfile, "%s.debug/%s", canon_dir, link); + einfo (VERBOSE2, "try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Try the first extra debug file root. */ + sprintf (debugfile, "%s/%s", DEBUGDIR_2, link); + einfo (VERBOSE2, "try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Try the first extra debug file root, with directory extensions. */ + sprintf (debugfile, "%s%s%s", DEBUGDIR_2, canon_dir, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Try the second extra debug file root. */ + sprintf (debugfile, "%s/%s", DEBUGDIR_3, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Try the third extra debug file root. */ + sprintf (debugfile, "%s/%s", DEBUGDIR_4, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Then try in the global debugfile directory. */ + sprintf (debugfile, "%s/%s", DEBUGDIR_1, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Then try in the global debugfile directory, with directory extensions. */ + sprintf (debugfile, "%s%s%s", DEBUGDIR_1, canon_dir, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* Try the first extra debug file root, with directory extensions. */ + sprintf (debugfile, "%s%s%s", DEBUGDIR_2, canon_dir, link); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + + /* FIMXE: This is a workaround for a bug in the Fedora packaging + system. It is possible for the debuginfo files to be out of + sync with their corresponding binary files. Eg ld-2.29.1-23.fc28 + vs ld-2.29.1-22.fc28.debug_info. So check for earlier versions + of the debuginfo file in the directory where it is known that + Fedora stores its debug files... */ + char * dash = strrchr (link, '-'); + if (dash) + { + char * end; + unsigned long revision = strtoul (dash + 1, & end, 10); + while (revision > 1) + { + --revision; + sprintf (debugfile, "%s%s%.*s%lu%s", DEBUGDIR_2, canon_dir, (int) (dash - link) + 1, link, revision, end); + einfo (VERBOSE2, " try: %s\n", debugfile); + if ((fd = open (debugfile, O_RDONLY)) != -1) + goto found; + } + } + + /* Failed to find the file. */ + einfo (VERBOSE, "%s: Could not find separate debug file: %s", data->filename, link); + + free (canon_dir); + free (debugfile); + return NULL; + + found: + /* FIXME: We should verify the CRC value... */ + + free (canon_dir); + + Dwarf * separate_debug_file; + + /* Now open the file.... */ + if ((separate_debug_file = dwarf_begin (fd, DWARF_C_READ)) == NULL) + { + einfo (VERBOSE, "%s: Failed to open separate debug file: %s", data->filename, debugfile); + free (debugfile); + return NULL; + } + + einfo (VERBOSE, "%s: Found separate debug info file: %s", data->filename, debugfile); + free (debugfile); + + /* Do not free debugfile - it might be referenced inside + the structure returned by open_debug_file(). */ + return separate_debug_file; +} + +/* -------------------------------------------------------------------- */ + +static bool +scan_dwarf (eu_checksec_data * data, Dwarf * dwarf, dwarf_walker func, void * ptr) +{ + Dwarf_Off cuoffset; + Dwarf_Off ncuoffset = 0; + size_t hsize; + + while (dwarf_nextcu (dwarf, cuoffset = ncuoffset, & ncuoffset, & hsize, NULL, NULL, NULL) == 0) + { + Dwarf_Off cudieoff = cuoffset + hsize; + Dwarf_Die cudie; + + if (dwarf_offdie (dwarf, cudieoff, & cudie) == NULL) + { + einfo (ERROR, "%s: Empty CU", data->filename); + continue; + } + + if (! func (data, dwarf, & cudie, ptr)) + return false; + } + + return true; +} + +/* Utility function to walk over the DWARF debug information in DATA calling FUNC + on each DIE. PTR is passed to FUNC along with a pointer to the DIE. + If FUNC returns false the walk is terminated. + Returns FALSE if the walk could not be executed. */ + +bool +eu_checksec_walk_dwarf (eu_checksec_data * data, dwarf_walker func, void * ptr) +{ + Dwarf * dwarf; + + if (! data->dwarf_searched) + { + dwarf = dwarf_begin (data->fd, DWARF_C_READ); + + if (dwarf == NULL) + dwarf = follow_debuglink (data, dwarf); + + data->dwarf_searched = true; + + if (dwarf == NULL) + return einfo (VERBOSE2, "%s: Does not contain any DWARF information", data->filename); + + data->dwarf = dwarf; + } + + if ((dwarf = data->dwarf) == NULL) + return true; + + if (! scan_dwarf (data, dwarf, func, ptr)) + { + /* Check for an alternate file. */ + Dwarf * alt = dwarf_getalt (dwarf); + + if (alt != NULL) + { + (void) dwarf_end (dwarf); + (void) scan_dwarf (data, alt, func, ptr); + data->dwarf = alt; + } + } + + /* We do not close the dwarf handle as we will probably want to use it again. */ + return true; +} + +/* -------------------------------------------------------------------- */ + +typedef struct walker_info +{ + ulong start; + ulong end; + const char ** name; + bool prefer_func; +} walker_info; + +static bool +find_symbol_addr_using_dwarf (eu_checksec_data * data, Dwarf * dwarf, Dwarf_Die * die, void * ptr) +{ + assert (data != NULL && die != NULL && ptr != NULL); + + walker_info * info; + size_t nlines; + Dwarf_Lines * lines; + + info = (walker_info *) ptr; + + dwarf_getsrclines (die, & lines, & nlines); + + if (lines != NULL && nlines > 0) + { + Dwarf_Line * line; + size_t indx = 1; + + einfo (VERBOSE2, "Scanning %ld lines in the DWARF line table", nlines); + while ((line = dwarf_onesrcline (lines, indx)) != NULL) + { + Dwarf_Addr addr; + + dwarf_lineaddr (line, & addr); + + if (addr >= info->start && addr <= info->end) + { + *(info->name) = dwarf_linesrc (line, NULL, NULL); + return false; + } + + ++ indx; + } + } + + return true; +} + +static const char * +find_symbol_in (eu_checksec_data * data, Elf_Scn * sym_sec, ulong addr, Elf64_Shdr * sym_hdr, bool prefer_func) +{ + Elf_Data * sym_data; + if ((sym_data = elf_getdata (sym_sec, NULL)) == NULL) + { + einfo (VERBOSE2, "No symbol section data"); + return NULL; + } + + bool use_sym = false; + bool use_saved = false; + GElf_Sym saved_sym; + GElf_Sym sym; + int symndx = 1; + + while (gelf_getsym (sym_data, symndx, & sym) != NULL) + { + if (sym.st_value == addr) + { + if (!prefer_func || ELF64_ST_TYPE (sym.st_info) == STT_FUNC) + { + use_sym = true; + break; + } + + memcpy (& saved_sym, & sym, sizeof sym); + use_saved = true; + continue; + } + + /* As of version 3 of the protocol, start symbols are set at base address plus 2. */ + if (!prefer_func && sym.st_value == addr + 2) + { + use_sym = true; + break; + } + + symndx++; + } + + if (use_sym) + return elf_strptr (data->elf, sym_hdr->sh_link, sym.st_name); + else if (use_saved) + return elf_strptr (data->elf, sym_hdr->sh_link, saved_sym.st_name); + else + return NULL; +} + +/* Return the name of a symbol most appropriate for address range START..END. + Returns NULL if no symbol could be found. */ + +const char * +eu_checksec_find_symbol_for_address_range (eu_checksec_data * data, eu_checksec_section * sec, ulong start, ulong end, bool prefer_func) +{ + static const char * previous_result; + static ulong previous_start; + static ulong previous_end; + + const char * name = NULL; + Elf64_Shdr sym_shdr; + Elf_Scn * sym_sec = NULL; + + if (start == previous_start && end == previous_end) + return previous_result; + + assert (data != NULL && sec != NULL); + + previous_start = start; + previous_end = end; + + einfo (VERBOSE2, "Look for a symbol matching address %#lx..%#lx", start, end); + + /* If the provided section has a link then try this first. */ + if (sec != NULL && sec->shdr.sh_link) + { + sym_sec = elf_getscn (data->elf, sec->shdr.sh_link); + read_section_header (data, sym_sec, & sym_shdr); + + if (sym_shdr.sh_type == SHT_SYMTAB || sym_shdr.sh_type == SHT_DYNSYM) + { + name = find_symbol_in (data, sym_sec, start, & sym_shdr, prefer_func); + if (name != NULL) + return previous_result = name; + } + } + + /* Search for symbol sections. */ + sym_sec = NULL; + + while ((sym_sec = elf_nextscn (data->elf, sym_sec)) != NULL) + { + read_section_header (data, sym_sec, & sym_shdr); + + if ((sym_shdr.sh_type == SHT_SYMTAB) || (sym_shdr.sh_type == SHT_DYNSYM)) + { + name = find_symbol_in (data, sym_sec, start, & sym_shdr, prefer_func); + if (name) + return previous_result = name; + } + } + + /* Now check DWARF data for an address match. */ + walker_info walker; + walker.start = start; + walker.end = end; + walker.name = & name; + walker.prefer_func = prefer_func; + eu_checksec_walk_dwarf (data, find_symbol_addr_using_dwarf, & walker); + + return previous_result = name; +} + +/* -------------------------------------------------------------------- */ + +static bool process_elf (const char *, int, Elf *); + +static bool +process_ar (const char * filename, int fd, Elf * elf) +{ + Elf * subelf; + Elf_Cmd cmd = ELF_C_READ_MMAP; + bool ret = true; + + while ((subelf = elf_begin (fd, cmd, elf)) != NULL) + { + /* Get the header for this element. */ + Elf_Arhdr * arhdr = elf_getarhdr (subelf); + const char * fname = concat (filename, ":", arhdr->ar_name, NULL); + + /* Skip over the index entries. */ + if (! streq (arhdr->ar_name, "/") + && ! streq (arhdr->ar_name, "//")) + ret = process_elf (fname, fd, subelf); + + /* Get next archive element. */ + cmd = elf_next (subelf); + + if (elf_end (subelf)) + return einfo (FAIL, "unable to close archive member %s", fname); + + free ((char *) fname); + } + + return ret; +} + +static bool +process_elf (const char * filename, int fd, Elf * elf) +{ + bool ret; + + switch (elf_kind (elf)) + { + case ELF_K_AR: + ret = process_ar (filename, fd, elf); + break; + case ELF_K_ELF: + ret = run_checkers (filename, fd, elf); + break; + default: + if (ignore_unknown) + return true; + return einfo (WARN, "%s: is not an ELF format file", filename); + } + + return ret; +} + +static bool +process_rpm_file (const char * filename) +{ + /* It turns out that the simplest/most portable way to handle an rpm is + to use the rpm2cpio and cpio programs to unpack it for us... */ + char dirname[20]; + + strcpy (dirname, "annocheck.XXXXXX"); + if (mkdtemp (dirname) == NULL) + return einfo (WARN, "Faield to create temporary directory for processing rpm: %s", filename); + + einfo (VERBOSE2, "Created temporary directory: %s", dirname); + + char * fname; + char * pname; + char * command; + char * cwd = getcwd (NULL, 0); + + if (filename[0] != '/') + fname = concat (cwd, "/", filename, NULL); + else + fname = concat (filename, NULL); + + if (progname[0] != '/' && strchr (progname, '/')) + pname = concat (cwd, "/", progname, NULL); + else + pname = concat (progname, NULL); + + command = concat (/* Change into the temporary directory. */ + "cd ", dirname, + /* Convert the rpm to cpio format. */ + " && rpm2cpio ", fname, + /* Pipe the output into cpio in order to extract the files. */ + " | cpio -dium --quiet", + /* Run annocheck on the files in the directory, skipping unknown file types, + and prefixing the output with the rpm name. */ + " && ", pname, " --ignore-unknown ", + "--prefix ", lbasename (filename), + " ", saved_args ? saved_args : "", + " .", + /* Then move out of the directory. */ + " && cd ..", + /* And delete it. */ + " && rm -r ", dirname, + NULL); + + einfo (VERBOSE2, "Running rpm extractor command sequence: %s", command); + if (system (command)) + return einfo (WARN, "Failed to process rpm file: %s", filename); + + free (command); + free (cwd); + free (fname); + free (pname); + + einfo (VERBOSE2, "Extraction successful"); + return true; +} + +static bool +process_file (const char * filename) +{ + size_t len; + struct stat statbuf; + + /* When ignoring unknown file types (which typically happens when processing the + contents of an rpm), we do not follow symbolic links. This allows us to detect + and ignore these links. */ + if ((ignore_unknown ? lstat (filename, & statbuf) : stat (filename, & statbuf)) < 0) + { + if (errno == ENOENT) + return einfo (WARN, "'%s': No such file", filename); + + return einfo (SYS_WARN, "Could not locate '%s'", filename); + } + + if (S_ISDIR (statbuf.st_mode)) + { + DIR * dir = opendir (filename); + + if (dir == NULL) + return einfo (SYS_WARN, "unable to read directory: %s", filename); + + struct dirent * entry; + bool result = true; + + einfo (VERBOSE, "Scanning directory: '%s'", filename); + while ((entry = readdir (dir)) != NULL) + { + if (streq (entry->d_name, ".") || streq (entry->d_name, "..")) + continue; + + /* FIXME: Memory leak... */ + result &= process_file (concat (filename, "/", entry->d_name, NULL)); + } + + closedir (dir); + return result; + } + + if (! S_ISREG (statbuf.st_mode)) + { + if (ignore_unknown) + return true; + + return einfo (WARN, "'%s' is not an ordinary file", filename); + } + + if (statbuf.st_size < 0) + return einfo (WARN, "'%s' has negative size, probably it is too large", filename); + + /* If the file is an RPM hand it off for separate processing. */ + + /* FIXME: the rpmReadPackageFile() function can generate a seg-fault + when processing some rpms (eg: wireshark-cli-2.6.0-3.fc27.aarch64.rpm) + so just check for a .rpm suffix first. */ + if ((len = strlen (filename)) > 4 && streq (filename + len - 4, ".rpm")) + return process_rpm_file (filename); + + FD_t rpm_fd; + rpmts ts = 0; + Header hdr; + if ((rpm_fd = Fopen (filename, "r")) != NULL + && rpmReadPackageFile (ts, rpm_fd, filename, & hdr) == RPMRC_OK) + { + bool res = process_rpm_file (filename); + Fclose (rpm_fd); + return res; + } + + /* Otherwise open it and try to process it as an ELF file. */ + int fd = open (filename, O_RDONLY); + if (fd == -1) + return einfo (SYS_WARN, "Could not open %s", filename); + + Elf * elf = elf_begin (fd, ELF_C_READ, NULL); + if (elf == NULL) + return einfo (WARN, "Unable to parse %s - maybe it is not an ELF file ?", filename); + + bool ret = process_elf (filename, fd, elf); + + if (elf_end (elf)) + return einfo (WARN, "Failed to close ELF file: %s", filename); + + if (close (fd)) + return einfo (SYS_WARN, "Unable to close: %s", filename); + + return ret; +} + +static bool +process_files (void) +{ + bool result = true; + + while (num_files) + result &= process_file (files [-- num_files]); + + return result; +} + +/* -------------------------------------------------------------------- */ + +int +main (int argc, const char ** argv) +{ + if (elf_version (EV_CURRENT) == EV_NONE) + { + einfo (FAIL, "Could not initialise libelf"); + return EXIT_FAILURE; + } + + if (rpmReadConfigFiles (NULL, NULL) != 0) + { + einfo (FAIL, "Could not initialise librpm"); + return EXIT_FAILURE; + } + + if (! process_command_line (argc, argv)) + return EXIT_FAILURE; + + if (!process_files ()) + { + /* FIXME: This is a hack. When --ignore-unknown is active we + are probably processing an rpm, and we do not want the + return status from annocheck to stop the cleanup of the + temporary directory. */ + if (ignore_unknown) + return EXIT_SUCCESS; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* -------------------------------------------------------------------- */ + +bool +eu_checksec_add_checker (struct checker * new_checker, uint major) +{ + if (major < major_version) + return false; + + checker_internal * internal = XCNEW (checker_internal); + + new_checker->internal = internal; + if (new_checker->interesting_sec) + { + internal->next_sec = first_sec_checker; + first_sec_checker = new_checker; + } + + if (new_checker->interesting_seg) + { + internal->next_seg = first_seg_checker; + first_seg_checker = new_checker; + } + + internal->next = first_checker; + first_checker = new_checker; + + return true; +} diff --git a/annocheck/annocheck.h b/annocheck/annocheck.h new file mode 100644 index 0000000..8076f85 --- /dev/null +++ b/annocheck/annocheck.h @@ -0,0 +1,224 @@ +/* Annocheck - A tool for checking security features of binares. + Copyright (c) 2018 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. + + 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. */ + +#ifndef __EU_CHECKSEC_H__ +#define __EU_CHECKSEC_H__ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define PACKAGE "annocheck" + +/* Values used by annobin notes. */ +#define GNU_BUILD_ATTRS_SECTION_NAME ".gnu.build.attributes" +#define NT_GNU_BUILD_ATTRIBUTE_OPEN 0x100 +#define NT_GNU_BUILD_ATTRIBUTE_FUNC 0x101 +#define GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC '*' +#define GNU_BUILD_ATTRIBUTE_TYPE_STRING '$' +#define GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE '+' +#define GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE '!' + +#define GNU_BUILD_ATTRIBUTE_VERSION 1 +#define GNU_BUILD_ATTRIBUTE_STACK_PROT 2 +#define GNU_BUILD_ATTRIBUTE_RELRO 3 +#define GNU_BUILD_ATTRIBUTE_STACK_SIZE 4 +#define GNU_BUILD_ATTRIBUTE_TOOL 5 +#define GNU_BUILD_ATTRIBUTE_ABI 6 +#define GNU_BUILD_ATTRIBUTE_PIC 7 +#define GNU_BUILD_ATTRIBUTE_SHORT_ENUM 8 + + +#define streq(a,b) (strcmp ((a), (b)) == 0) +#define strneq(a,b,n) (strncmp ((a), (b), (n)) == 0) +#define const_strneq(a,b) (strncmp ((a), (b), sizeof (b) - 1) == 0) + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef struct eu_checksec_data +{ + const char * filename; + const char * full_filename; + int fd; + Elf * elf; + bool is_32bit; + Elf_Data * syms; + Dwarf * dwarf; + bool dwarf_searched; +} eu_checksec_data; + +typedef struct eu_checksec_section +{ + const char * secname; + Elf_Scn * scn; + Elf64_Shdr shdr; + Elf_Data * data; +} eu_checksec_section; + +typedef struct eu_checksec_segment +{ + GElf_Phdr * phdr; + uint number; + Elf_Data * data; +} eu_checksec_segment; + +typedef struct checker +{ + const char * name; + + /* Called before starting the check of a file. + Can be NULL. + The section_headers and segment_headers fields will not have been initialised. */ + void (* start) (eu_checksec_data * DATA); + + /* Called to see if the checker is interested in the particular section. + Can be NULL. If NULL, all sections are ignored. + If FALSE is returned the section is not processed any further. + Note - called even if there are segments in the file. + Note - SECTION->data may not be initialised at this point. */ + bool (* interesting_sec) (eu_checksec_data * DATA, + eu_checksec_section * SECTION); + + /* Called to check a section. + If interesting_sec is not NULL, then this field cannot be NULL. + If FALSE is returned the check is considered to have failed. + Note - SECTION->data will be initialised at this point. */ + bool (* check_sec) (eu_checksec_data * DATA, + eu_checksec_section * SECTION); + + /* Called before checking a segment. + Can be NULL. If NULL, all segments are ignored. + If FALSE is returned the segment is not processed any further. + Note - called even if there are sections in the file. + The SEG->DATA field may not have beeen initialised. */ + bool (* interesting_seg) (eu_checksec_data * DATA, + eu_checksec_segment * SEG); + + /* Called to check a segment. + If interesting_seg is not NULL, then this field cannot be NULL. + If FALSE is returned the check is considered to have failed. + the SEG->DATA field will have been initialised. */ + bool (* check_seg) (eu_checksec_data * DATA, + eu_checksec_segment * SEG); + + /* Called at the end of checking a file. + Can be NULL. + Returns a success/fail status for the entire scan. */ + bool (* finish) (eu_checksec_data * DATA); + + /* Called to allow the callback a chance to handle its own command line arguments. + Can be NULL. */ + bool (* process_arg) (const char * ARG, const char ** ARGV, const uint ARGC, uint * NEXT_INDX); + + /* Called to add additional text to the --help output. + Should include a short description of what the checker does. + Can be NULL. + Should use einfo to display its information. */ + void (* usage) (void); + + /* Called to display the version of the checker. + Can be NULL. + Should use einfo to display its information. */ + void (* version) (void); + + /* Pointer to internal data used by the checksec framework. + This field should not be used by the checker. */ + void * internal; + +} checker; + +#undef PTR + +/* Type for the ELF note walker. */ +typedef bool (* note_walker) (eu_checksec_data * DATA, + eu_checksec_section * SEC, + GElf_Nhdr * NOTE, + size_t NAME_OFFSET, + size_t DESC_OFFSET, + void * PTR); + +/* Walks over the notes in SECTION, applying FUNC to each. + Stops if FUNC returns FALSE. + Passes PTR to FUNC along with a pointer to the note and the offsets to the name and desc data fields. + Returns FALSE if it could not walk the notes. */ +extern bool eu_checksec_walk_notes (eu_checksec_data * DATA, eu_checksec_section * SEC, note_walker FUNC, void * PTR); + +/* Type for the DWARF DIE walker. */ +typedef bool (* dwarf_walker) (eu_checksec_data * DATA, Dwarf * DWARF, Dwarf_Die * DIE, void * PTR); + +/* Walks over the DWARF DIEs in DATA, applying FUNC to each. + Stops if FUNC returns FALSE. + Passes PTR to FUNC along with a pointer to the DIE. + Returns FALSE if it could not walk the debug information. */ +extern bool eu_checksec_walk_dwarf (eu_checksec_data * DATA, dwarf_walker FUNC, void * PTR); + +/* Called to register a checker. + Returns FALSE if the checker could not be registered. + Can be called from static constructors. + The MAJOR version number is used to verify that the checker is compatible with the framework. */ +extern bool eu_checksec_add_checker (struct checker * CHECKER, uint MAJOR); + +/* Return the name of a symbol most appropriate for address START..END. + Returns NULL if no symbol could be found. */ +extern const char * eu_checksec_find_symbol_for_address_range + (eu_checksec_data * DATA, eu_checksec_section * SEC, ulong START, ulong ADDR, bool PREFER_FUNC); + +/* An enum controlling the behaviour of the einfo function: */ +typedef enum einfo_type +{ + WARN, /* Issues a warning message. */ + SYS_WARN, /* Like WARN but also prints out errno. */ + ERROR, /* Issues an error message. */ + SYS_ERROR, /* Like ERROR but also prints out errno. */ + FAIL, /* Like ERROR but also calls abort(). */ + INFO, /* Prints an informative message (on stdout). */ + VERBOSE, /* Like INFO but only generates the message if verbose is set. */ + VERBOSE2, /* Like VERBOSE but only generates the message if verbose was set twice. */ + PARTIAL /* Like INFO but no EOL required. */ +} einfo_type; + +/* A printf like function for displaying text. */ +extern bool einfo (einfo_type, const char *, ...) ATTRIBUTE_PRINTF(2, 3); + +/* How informative we should be. */ +extern ulong verbosity; + +#define BE_VERY_VERBOSE (verbosity > 1) +#define BE_VERBOSE (verbosity > 0) +#define BE_QUIET (verbosity == -1UL) + +/* The version numbers of the checksec framework. */ +extern uint major_version; +extern uint minor_version; + +#endif /* __EU_CHECKSEC_H__ */ diff --git a/annocheck/built-by.c b/annocheck/built-by.c new file mode 100644 index 0000000..30a36ff --- /dev/null +++ b/annocheck/built-by.c @@ -0,0 +1,246 @@ +/* Checks the builder of the binary file. + Copyright (c) 2018 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 . + + 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 "annocheck.h" + +static const char * istool = NULL; +static const char * nottool = NULL; + +static bool disabled = true; + +static bool found_builder; +static bool all = false; + +static void +builtby_start (eu_checksec_data * data) +{ + found_builder = false; +} + +static bool +builtby_interesting_sec (eu_checksec_data * data, + eu_checksec_section * sec) +{ + if (disabled) + return false; + + if (! all && found_builder) + return false; + + if (streq (sec->secname, ".comment")) + return true; + + return sec->shdr.sh_type == SHT_NOTE; +} + +static bool +found (const char * source, const char * filename, const char * tool) +{ + /* FIXME: Regexps would be better. */ + if (nottool != NULL && streq (nottool, tool)) + return true; + + if (istool != NULL && ! streq (istool, tool)) + return true; + + if (all) + einfo (INFO, "%s was built by %s [%s]", filename, tool, source); + else + einfo (INFO, "%s was built by %s", filename, tool); + + found_builder = true; + return all; /* Stop further searches unless checking for all builder notes. */ +} + +static bool +builtby_note_walker (eu_checksec_data * data, + eu_checksec_section * sec, + GElf_Nhdr * note, + size_t name_offset, + size_t data_offset, + void * ptr) +{ + if (note->n_type != NT_GNU_BUILD_ATTRIBUTE_OPEN) + return true; + + if (note->n_namesz < 3) + return false; + + const char * namedata = sec->data->d_buf + name_offset; + + uint pos = (namedata[0] == 'G' ? 3 : 1); + + /* Look for: GA$gcc 7.0.0 20161212. */ + if (namedata[pos] != GNU_BUILD_ATTRIBUTE_TOOL) + return true; + + if (namedata[pos - 1] != GNU_BUILD_ATTRIBUTE_TYPE_STRING) + return false; + + return found ("annobin note", (const char *) ptr, namedata + pos + 1); +} + +static bool +builtby_check_sec (eu_checksec_data * data, + eu_checksec_section * sec) +{ + if (streq (sec->secname, ".comment")) + return found (".comment section", data->filename, (const char *) sec->data->d_buf); + + if (streq (sec->secname, GNU_BUILD_ATTRS_SECTION_NAME)) + return eu_checksec_walk_notes (data, sec, builtby_note_walker, (void *) data->filename); + + return true; /* Allow the search to continue. */ +} + +/* Look for DW_AT_producer attributes. */ + +static bool +builtby_dwarf_walker (eu_checksec_data * data, Dwarf * dwarf, Dwarf_Die * die, void * ptr) +{ + Dwarf_Attribute attr; + const char * string; + + if (dwarf_attr (die, DW_AT_producer, & attr) == NULL) + return true; + + string = dwarf_formstring (& attr); + if (string == NULL) + return einfo (ERROR, "%s: DWARF DW_AT_producer attribute does not have a string value", data->filename); + + found ("DWARF attribute", data->filename, string); + return all; +} + +static bool +builtby_finish (eu_checksec_data * data) +{ + if (disabled) + return true; + + if (found_builder && ! all) + return true; + + (void) eu_checksec_walk_dwarf (data, builtby_dwarf_walker, NULL); + + if (! found_builder) + einfo (INFO, "%s: could not determine builder", data->filename); + + return true; +} + +static bool +builtby_process_arg (const char * arg, const char ** argv, const uint argc, uint * next) +{ + const char * parameter; + + if (streq (arg, "--enable-builtby")) + { + disabled = false; + return true; + } + + if (streq (arg, "--disable-builtby")) + { + disabled = true; + return true; + } + + if (streq (arg, "--all")) + { + all = true; + return true; + } + + if (const_strneq (arg, "--tool=")) + { + if ((parameter = strchr (arg, '=')) == NULL) + { + istool = argv[* next]; + * next = * next + 1; + } + else + istool = parameter + 1; + + return true; + } + + if (const_strneq (arg, "--nottool=")) + { + if ((parameter = strchr (arg, '=')) == NULL) + { + nottool = argv[* next]; + * next = * next + 1; + } + else + nottool = parameter + 1; + return true; + } + + return false; +} + +static void +builtby_usage (void) +{ + einfo (INFO, "Determines what tool built the given file(s)"); + einfo (INFO, " NOTE: This tool is disabled by default. To enable it use: --enable-builtby"); + einfo (INFO, " The checks can be made conditional by using the following options:"); + einfo (INFO, " --all Report all builder identification strings"); + einfo (INFO, " --tool= Only report binaries built by "); + einfo (INFO, " --nottool= Skip binaries built by "); +#if 0 + einfo (INFO, " --before= Only report binaries built before "); + einfo (INFO, " --after= Only report binaries built after "); + einfo (INFO, " --minver= Only report binaries built by version or higher"); + einfo (INFO, " --maxver= Only report binaries built by version or lower"); + einfo (INFO, " is just a string, not a regular expression"); + einfo (INFO, " format is YYYYMMDD. For example: 20161230"); + einfo (INFO, " is a version string in the form V.V.V For example: 6.1.2"); + einfo (INFO, "The --before and --after options can be used together to specify a date"); + einfo (INFO, "range which should be reported. Similarly the --minver and --maxver"); + einfo (INFO, "options can be used together to specify a version range.\n"); +#endif +} + +static void +builtby_version (void) +{ + einfo (INFO, "Version 1.0"); +} + +struct checker builtby_checker = +{ + "Built By", + builtby_start, + builtby_interesting_sec, + builtby_check_sec, + NULL, /* interesting_seg */ + NULL, /* check_seg */ + builtby_finish, + builtby_process_arg, + builtby_usage, + builtby_version, + NULL /* internal */ +}; + +static __attribute__((constructor)) void +builtby_register_checker (void) +{ + if (! eu_checksec_add_checker (& builtby_checker, major_version)) + disabled = true; +} diff --git a/annocheck/hardened.c b/annocheck/hardened.c new file mode 100644 index 0000000..0acc1e7 --- /dev/null +++ b/annocheck/hardened.c @@ -0,0 +1,1061 @@ +/* Checks the hardened status of the given file. + Copyright (c) 2018 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 . + + 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 "annocheck.h" + +/* Set by the constructor. */ +static bool disabled = false; + +/* These are initialised on a per-input-file basis by start(). */ +static bool i686_found; +static bool x86_found; +static bool arm_found; +static bool debuginfo_file; + +static bool bind_now_found; +static bool dynamic_segment_found; +static bool gnu_relro_found; +static bool et_exec_found; +static bool gnu_stack_found; +static bool rwx_seg_found; +static bool textrel_found; +static bool bad_run_path_found; +static bool gap_detected; +static bool thread_cancellation; +static bool writable_got_relocations; +static int stack_realign; +static int pic_level; +static int stack_protection; +static int fortify_level; +static int final_fortify_level; +static int optimization_level; +static int glibcxx_assertions; +static int stack_clash_protection; +static int cf_protection; +static int num_fails; +static int num_maybes; +static int gcc_version; + +#define UNKNOWN -1 +#define YES 1 +#define NO 0 +#define BOTH -2 + +static void +start (eu_checksec_data * data) +{ + debuginfo_file = false; + bind_now_found = false; + dynamic_segment_found = false; + gnu_relro_found = false; + gnu_stack_found = false; + rwx_seg_found = false; + textrel_found = false; + bad_run_path_found = false; + gap_detected = false; + thread_cancellation = true; + writable_got_relocations = false; + + pic_level = UNKNOWN; + cf_protection = UNKNOWN; + stack_clash_protection = UNKNOWN; + stack_protection = UNKNOWN; + final_fortify_level = fortify_level = UNKNOWN; + optimization_level = UNKNOWN; + glibcxx_assertions = UNKNOWN; + stack_realign = UNKNOWN; + gcc_version = UNKNOWN; + + num_fails = 0; + num_maybes = 0; + + if (data->is_32bit) + { + Elf32_Ehdr * hdr = elf32_getehdr (data->elf); + + et_exec_found = hdr->e_type == ET_EXEC; + x86_found = hdr->e_machine == EM_386; + i686_found = x86_found; + arm_found = hdr->e_machine == EM_ARM; + } + else + { + Elf64_Ehdr * hdr = elf64_getehdr (data->elf); + + et_exec_found = hdr->e_type == ET_EXEC; + x86_found = hdr->e_machine == EM_X86_64; + i686_found = false; + arm_found = false; + } +} + +static bool +interesting_sec (eu_checksec_data * data, + eu_checksec_section * sec) +{ + if (disabled) + return false; + + /* .dwz files have a .gdb_index section. */ + if (streq (sec->secname, ".gdb_index")) + debuginfo_file = true; + + if (streq (sec->secname, ".text")) + { + /* Separate debuginfo files have a .text section with a non-zero + size but no contents! */ + if (sec->shdr.sh_type == SHT_NOBITS && sec->shdr.sh_size > 0) + debuginfo_file = true; + + return false; + } + else if (debuginfo_file) + return false; + + /* If the file has a stack section then check its permissions. */ + if (streq (sec->secname, ".stack")) + if ((sec->shdr.sh_flags & (SHF_WRITE | SHF_EXECINSTR)) == SHF_WRITE) + gnu_stack_found = true; + + /* Note the permissions on GOT/PLT relocation sections. */ + if (streq (sec->secname, ".rel.got") + || streq (sec->secname, ".rela.got") + || streq (sec->secname, ".rel.plt") + || streq (sec->secname, ".rela.plt")) + { + if (sec->shdr.sh_flags & SHF_WRITE) + writable_got_relocations = true; + } + + return sec->shdr.sh_type == SHT_DYNAMIC + || sec->shdr.sh_type == SHT_NOTE + || sec->shdr.sh_type == SHT_STRTAB; +} + +static inline unsigned long +align (unsigned long val, unsigned long alignment) +{ + return (val + (alignment - 1)) & (~ (alignment - 1)); +} + +typedef struct hardened_note_data +{ + ulong prev_end; + ulong start; + ulong end; +} hardened_note_data; + +static const char * +get_component_name (eu_checksec_data * data, + eu_checksec_section * sec, + hardened_note_data * note_data, + bool inc_addr, + bool prefer_func_symbol) +{ + static char buffer[256]; + const char * sym = eu_checksec_find_symbol_for_address_range (data, sec, note_data->start, note_data->end, prefer_func_symbol); + + if (sym == NULL || inc_addr) + sprintf (buffer, "addr range: %#lx..%#lx ", note_data->start, note_data->end); + else + buffer[0] = 0; + + if (sym) + { + strcat (buffer, "component: "); + strcat (buffer, sym); + } + + return buffer; +} + +static const char * +stack_prot_type (uint value) +{ + switch (value) + { + case 0: return "-fno-stack-protector"; + case 1: return "-fstack-protector"; + case 2: return "-fstack-protector-all"; + case 3: return "-fstack-protector-strong"; + case 4: return "-fstack-protector-explicit"; + default: return ""; + } +} + +static bool +walk_notes (eu_checksec_data * data, + eu_checksec_section * sec, + GElf_Nhdr * note, + size_t name_offset, + size_t data_offset, + void * ptr) +{ + bool prefer_func_name; + hardened_note_data * note_data; + + if (note->n_type != NT_GNU_BUILD_ATTRIBUTE_OPEN + && note->n_type != NT_GNU_BUILD_ATTRIBUTE_FUNC) + return true; + + prefer_func_name = note->n_type == NT_GNU_BUILD_ATTRIBUTE_FUNC; + note_data = (hardened_note_data *) ptr; + + if (note->n_descsz > 0) + { + ulong start = 0; + ulong end = 0; + const unsigned char * descdata = sec->data->d_buf + data_offset; + + /* FIXME: Should we add support for earlier versions of + the annobin notes which did not include an end symbol ? */ + + if (note->n_descsz == 16) + { + int i; + int shift; + + for (shift = i = 0; i < 8; i++) + { + ulong byte; + + byte = descdata[i]; + start |= byte << shift; + + byte = descdata[i + 8]; + end |= byte << shift; + + shift += 8; + } + } + else if (note->n_descsz == 8) + { + start = descdata[0] | (descdata[1] << 8) | (descdata[2] << 16) | (descdata[3] << 24); + end = descdata[4] | (descdata[5] << 8) | (descdata[6] << 16) | (descdata[7] << 24); + } + else + return false; + + if (note->n_type == NT_GNU_BUILD_ATTRIBUTE_OPEN) + { + if (note_data->prev_end > 0 + && start > align (note_data->prev_end, 16)) + { + hardened_note_data fake_note; + + fake_note.start = note_data->prev_end + 1; + fake_note.end = start; + + /* Note - we ignore gaps at the start and end of the file. These are + going to be from the crt code which does not need to be chacked. */ + einfo (VERBOSE, "%s: GAP: (%s) in annobin notes", + data->filename, get_component_name (data, sec, & fake_note, true, prefer_func_name)); + gap_detected = true; + } + + note_data->prev_end = end; + } + + note_data->start = start; + note_data->end = end; + } + + if (note->n_namesz < 3) + return false; + + const char * namedata = sec->data->d_buf + name_offset; + uint pos = (namedata[0] == 'G' ? 3 : 1); + char attr_type = namedata[pos - 1]; + const char * attr = namedata + pos; + + /* Advance pos to the attribute's value. */ + if (! isprint (* attr)) + pos ++; + else + pos += strlen (namedata + pos) + 1; + + const char * string = namedata + pos; + uint value = UNKNOWN; + + switch (attr_type) + { + case GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC: + { + uint shift = 0; + int bytes = (namedata + note->n_namesz) - string; + + value = 0; + if (bytes > 0) + bytes --; + while (bytes --) + { + uint byte = (* string ++) & 0xff; + + value |= byte << shift; + shift += 8; + } + } + break; + case GNU_BUILD_ATTRIBUTE_TYPE_STRING: + break; + case GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE: + value = 1; + break; + case GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE: + value = 0; + break; + default: + return false; + } + + switch (* attr) + { + case GNU_BUILD_ATTRIBUTE_TOOL: + if (value != UNKNOWN) + einfo (VERBOSE, "ICE: The tool note should have a string attribute"); + else + { + /* Parse the tool attribute looking for the version of gcc used to build the component. */ + const char * gcc = strstr (attr, "gcc"); + if (gcc) + { + /* FIXME: This assumes that the tool string looks like: "gcc 7.x.x......" */ + unsigned long version = strtoul (gcc + 4, NULL, 10); + + einfo (VERBOSE, "%s: (%s) built-by gcc version %u", + data->filename, + get_component_name (data, sec, note_data, false, prefer_func_name), + version); + + if (gcc_version == UNKNOWN) + gcc_version = version; + else if (gcc_version != version) + { + einfo (VERBOSE, "%s: Warning: Multiple versions of gcc were used to build this file - the highest version will be used", + data->filename); + if (gcc_version < version) + gcc_version = version; + } + } + } + break; + + case GNU_BUILD_ATTRIBUTE_PIC: + if (value == UNKNOWN) + return false; + if (pic_level == UNKNOWN) + pic_level = value; + else if (pic_level != value) + { + einfo (VERBOSE, "%s: mayb: (%s): Compiled with a different -fPIC/-fPIE setting", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + /* Remember the lowest PIC level. */ + pic_level = value < pic_level ? value : pic_level; + } + break; + + case GNU_BUILD_ATTRIBUTE_STACK_PROT: + if (value == UNKNOWN) + return false; + + /* We know that __libc_csu_init cannot be compiled with stack protection + enabled because it is part of glibc's start up code. So do not complain. */ + if (streq (get_component_name (data, sec, note_data, false, false), "__libc_csu_init")) + break; + + switch (value) + { + case 0: /* NONE */ + einfo (VERBOSE, "%s: fail: (%s): No stack protection enabled", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + break; + case 1: /* BASIC (funcs using alloca or with local buffers > 8 bytes) */ + case 4: /* EXPLICIT */ + einfo (VERBOSE, "%s: fail: (%s): Insufficient stack protection: %s", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name), + stack_prot_type (value)); + break; + case 2: /* ALL */ + case 3: /* STRONG */ + break; + default: + einfo (VERBOSE, "ICE: Unexpected stack protection level of %d", value); + return false; + } + + if (stack_protection == UNKNOWN) + stack_protection = value; + else if (stack_protection == BOTH) + ; + else if (stack_protection != value) + { + if ((value == 2 && stack_protection == 3) + || (value == 3 && stack_protection == 2)) + ; + else + /* No need for a warning - the switch above will have handled that. */ + stack_protection = BOTH; + } + break; + + case 'c': + if (streq (attr, "cf_protection")) + { + if (value == UNKNOWN) + return false; + + switch (value) + { + default: + einfo (INFO, "%s: ICE: Unexpected value for cf-protection: %d", data->filename, value); + break; + case 4: + case 8: + if (cf_protection != BOTH) + cf_protection = YES; + break; + case 2: + case 6: + if (x86_found) + einfo (VERBOSE, "%s: fail: (%s): Only compiled with -fcf-protection=branch", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + cf_protection = BOTH; + break; + case 3: + case 7: + if (x86_found) + einfo (VERBOSE, "%s: fail: (%s): Only compiled with -fcf-protection=return", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + cf_protection = BOTH; + break; + case 5: + case 1: + if (x86_found) + einfo (VERBOSE, "%s: fail: (%s): Compiled without -fcf-protection", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + cf_protection = BOTH; + break; + } + } + + case 'F': + if (streq (attr, "FORTIFY")) + { + if (value == UNKNOWN) + return false; + else if (fortify_level == UNKNOWN) + final_fortify_level = fortify_level = value; + else if (fortify_level != value) + { + /* A change in the FORTIFY level has been detected! */ + switch (fortify_level) + { + case 0: + case 2: + einfo (VERBOSE, "%s: fail: (%s): Change in _FORTIFY_SOURCE level from %d to %d", + data->filename, + get_component_name (data, sec, note_data, false, prefer_func_name), + fortify_level, value); + break; + default: + einfo (VERBOSE, "ICE: Unexpected FORTIFY level of %d", fortify_level); + break; + } + + fortify_level = value; + final_fortify_level = BOTH; + } + } + break; + + case 'G': + if (streq (attr, "GOW")) + { + if (value == UNKNOWN) + return false; + + value = (value >> 9) & 3; + + if (optimization_level == UNKNOWN) + optimization_level = value; + + if (value == 0 || value == 1) + { + einfo (VERBOSE, "%s: fail: (%s): Insufficient optimization level: -O%d", + data->filename, + get_component_name (data, sec, note_data, false, prefer_func_name), + value); + optimization_level = value; + } + } + else if (streq (attr, "GLIBCXX_ASSERTIONS")) + { + switch (value) + { + case 0: + einfo (VERBOSE, "%s: fail: (%s): Compiled without -D_GLIBCXX_ASSERTIONS", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + if (glibcxx_assertions == UNKNOWN) + glibcxx_assertions = NO; + else if (glibcxx_assertions == YES) + glibcxx_assertions = BOTH; + break; + case 1: + if (glibcxx_assertions == UNKNOWN) + glibcxx_assertions = YES; + break; + default: + einfo (VERBOSE, "ICE: Unexpected GLIBCXX_ASSERTIONS value: %d", value); + return false; + } + } + break; + + case 's': + if (streq (attr, "stack_clash")) + { + switch (value) + { + case 0: + if (! arm_found) + einfo (VERBOSE, "%s: fail: (%s): Compiled without -fstack-clash-protection", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + stack_clash_protection = NO; + break; + case 1: + if (stack_clash_protection == UNKNOWN) + stack_clash_protection = YES; + break; + default: + if (! arm_found) + einfo (VERBOSE, "ICE: Unexpected stack-clash value: %d", value); + return false; + } + } + else if (streq (attr, "stack_realign")) + { + if (stack_realign == UNKNOWN) + stack_realign = value; + else if (stack_realign != value) + { + if (value == NO) + einfo (VERBOSE, "%s: fail: (%s): Stack realignment not enabled", + data->filename, get_component_name (data, sec, note_data, false, prefer_func_name)); + stack_realign = BOTH; + } + } + break; + + default: + break; + } + + return true; +} + +static bool +check_note_section (eu_checksec_data * data, + eu_checksec_section * sec) +{ + if (streq (sec->secname, GNU_BUILD_ATTRS_SECTION_NAME)) + { + hardened_note_data hard_data; + + hard_data.start = 0; + hard_data.end = 0; + hard_data.prev_end = 0; + + return eu_checksec_walk_notes (data, sec, walk_notes, (void *) & hard_data); + } + + return true; +} + +static bool +check_string_section (eu_checksec_data * data, + eu_checksec_section * sec) +{ + /* Check the string table to see if it contains "__pthread_register_cancel". + This is not as accurate as checking for a function symbol with this name, + but it is a lot faster. */ + if (strstr ((const char *) sec->data->d_buf, "__pthread_register_cancel")) + thread_cancellation = NO; + + return true; +} + +/* Returns TRUE iff STR contains a search path that does not start with /usr. */ + +static bool +not_rooted_at_usr (const char * str) +{ + while (str) + { + if (! const_strneq (str, "/usr")) + return true; + str = strchr (str, ':'); + if (str) + str++; + } + return false; +} + +static bool +check_dynamic_section (eu_checksec_data * data, + eu_checksec_section * sec) +{ + size_t num_entries = sec->shdr.sh_size / sec->shdr.sh_entsize; + + /* Walk the dynamic tags. */ + while (num_entries --) + { + GElf_Dyn dynmem; + GElf_Dyn * dyn = gelf_getdyn (sec->data, num_entries, & dynmem); + + if (dyn == NULL) + break; + + if (dyn->d_tag == DT_BIND_NOW) + bind_now_found = true; + + if (dyn->d_tag == DT_FLAGS) + { + if (dyn->d_un.d_val & DF_BIND_NOW) + bind_now_found = true; + } + + if (dyn->d_tag == DT_TEXTREL) + textrel_found = true; + + if (dyn->d_tag == DT_RPATH || dyn->d_tag == DT_RUNPATH) + if (not_rooted_at_usr (elf_strptr (data->elf, sec->shdr.sh_link, dyn->d_un.d_val))) + bad_run_path_found = true; + } + + return true; +} + +static bool +check_sec (eu_checksec_data * data, + eu_checksec_section * sec) +{ + /* Note - the types checked here should correspond to the types + selected in interesting_sec(). */ + switch (sec->shdr.sh_type) + { + case SHT_NOTE: return check_note_section (data, sec); + case SHT_STRTAB: return check_string_section (data, sec); + case SHT_DYNAMIC: return check_dynamic_section (data, sec); + default: break; + } + + return true; +} + +static bool +interesting_seg (eu_checksec_data * data, + eu_checksec_segment * seg) +{ + if (disabled) + return false; + + switch (seg->phdr->p_type) + { + case PT_GNU_RELRO: + gnu_relro_found = true; + break; + + case PT_GNU_STACK: + gnu_stack_found = true; + break; + + case PT_DYNAMIC: + dynamic_segment_found = true; + break; + + default: + break; + } + + if ((seg->phdr->p_flags & (PF_X | PF_W | PF_R)) == (PF_X | PF_W | PF_R)) + { + einfo (VERBOSE, "%s: fail: seg %d has Read, Write and eXecute flags\n", + data->filename, seg->number); + rwx_seg_found = true; + } + + return false; +} + +static void +fail (eu_checksec_data * data, const char * message) +{ + einfo (INFO, "%s: FAIL: %s", data->filename, message); + ++ num_fails; +} + +static void +maybe (eu_checksec_data * data, const char * message) +{ + einfo (INFO, "%s: MAYB: %s", data->filename, message); + ++ num_maybes; +} + +static void +pass (eu_checksec_data * data, const char * message) +{ + einfo (VERBOSE, "%s: pass: %s", data->filename, message); +} + +static void +ice (eu_checksec_data * data, const char * message) +{ + einfo (INFO, "%s: internal error: %s", data->filename, message); +} + +static bool +finish (eu_checksec_data * data) +{ + if (disabled || debuginfo_file) + return true; + + if (gap_detected) + { + if (! BE_VERBOSE) + maybe (data, "Gaps were detected in the annobin coverage. Run with -v to list"); + else + maybe (data, "Gaps were detected in the annobin coverage"); + } + + if (bind_now_found) + pass (data, "Linked with -Wl,-z,now"); + else + fail (data, "Not linked with -Wl,-z,now"); + + if (dynamic_segment_found) + pass (data, "Dynamic segment is present"); + else + maybe (data, "Dynamic segment is absent"); + + if (writable_got_relocations) + fail (data, "Relocations for the GOT/PLT sections are writeable"); + else + pass (data, "GOT/PLT relocations are read only"); + + if (gnu_relro_found) + pass (data, "Linked with -Wl,-z,relro"); + else + fail (data, "Not linked with -Wl,-z,relro"); + + if (gnu_stack_found) + pass (data, "Stack not executable"); + else + fail (data, "Executable stack found ?"); + + if (rwx_seg_found) + fail (data, "RWX segment found"); + else + pass (data, "No RWX segments found"); + + if (textrel_found) + fail (data, "Text relocations found"); + else + pass (data, "No text relocations found"); + + if (bad_run_path_found) + fail (data, "DT_RPATH/DT_RUNPATH contains directories not starting with /usr"); + else + pass (data, "DT_RPATH/DT_RUNPATH absent or rooted at /usr"); + + if (thread_cancellation) + pass (data, "No thread cancellation problems"); + else + fail (data, "Thread cancellation not hardened. (Compiled without -fexceptions)"); + + /* Check PIC/PIE. */ + switch (pic_level) + { + case UNKNOWN: + maybe (data, "PIC/PIE setting not recorded"); + break; + case 0: + fail (data, "Compiled without any PIC option"); + break; + case 1: + case 2: + if (et_exec_found) + fail (data, "Compiled with PIC rather than PIE"); + else + pass (data, "Compiled with PIC"); + break; + case 3: + case 4: + pass (data, "Compiled with PIE"); + break; + default: + ice (data, "Unknown PIC level"); + break; + } + + switch (stack_protection) + { + case BOTH: + if (! BE_VERBOSE) + fail (data, "Conflicting stack protection settings. Run with -v to list"); + else + fail (data, "Conflicting stack protection settings."); + break; + case UNKNOWN: + fail (data, "Stack protection status is not recorded"); + break; + case 2: + case 3: + pass (data, "Strong stack protection is enabled"); + break; + case NO: + fail (data, "Stack protection has not been enabled"); + break; + case 1: + case 4: + fail (data, "Stack protection is not strong enough"); + break; + default: + maybe (data, "stack protection has an unknown value"); + break; + } + + if (! arm_found) + { + switch (stack_clash_protection) + { + case UNKNOWN: + if (gcc_version >= 7) + maybe (data, "-fstack-clash-protection not recorded"); + break; + case NO: + fail (data, "-fstack-clash-protection not used"); + break; + case YES: + pass (data, "Compiled with -fstack-clash-protection"); + break; + default: + ice (data, "stack-clash notes are incorrect"); + break; + } + } + + switch (final_fortify_level) + { + case BOTH: + if (! BE_VERBOSE) + fail (data, "Conflicting -D_FORTIFY_SOURCE levels. Run with -v to list"); + else + fail (data, "Conflicting -D_FORTIFY_SOURCE levels"); + break; + case UNKNOWN: + fail (data, "-D_FORTIFY_SOURCE level not recorded"); + break; + case 2: + pass (data, "-D_FORTIFY_SOURCE=2 specified"); + break; + case 0: + case 1: + fail (data, "-D_FORTIFY_SOURCE level too small"); + break; + default: + maybe (data, "-D_FORTIFY_SOURCE level unexpectedly large"); + break; + } + + switch (optimization_level) + { + case UNKNOWN: + fail (data, "Optimization level not recorded"); + break; + case 3: + case 2: + pass (data, "Sufficient compiler optimization used"); + break; + case 0: + case 1: + fail (data, "Insufficient compiler optimization"); + break; + default: + maybe (data, "Optimization level unexpectedly large"); + break; + } + + switch (glibcxx_assertions) + { + case BOTH: + if (! BE_VERBOSE) + fail (data, "Some components not compiled with -D_GLIBCXX_ASSERTONS. Run with -v to list"); + else + fail (data, "Some components not compiled with -D_GLIBCXX_ASSERTONS"); + break; + case UNKNOWN: + maybe (data, "-D_GLIBCXX_ASSERTIONS not recorded"); + break; + case NO: + fail (data, "-D_GLIBCXX_ASSERTIONS not used"); + break; + case YES: + pass (data, "Compiled with -D_GLIBCXX_ASSERTIONS"); + break; + default: + ice (data, "glibcxx_assertion notes incorrect"); + break; + } + + if (x86_found) + { + switch (cf_protection) + { + case UNKNOWN: + if (gcc_version >= 8) + maybe (data, "-fcf-protection not recorded"); + break; + case BOTH: + if (! BE_VERBOSE) + fail (data, "Some components compiled without -fcf-protection. Run with -v to list"); + else + fail (data, "Some components compiled without -fcf-protection"); + break; + case NO: + fail (data, "-fcf-protection not enabled"); + break; + case YES: + pass (data, "Compiled with -fcf-protection=full"); + break; + default: + ice (data, "cf_protection notes are incorrect"); + break; + } + } + + if (i686_found) + { + switch (stack_realign) + { + case UNKNOWN: + maybe (data, "-mstackrealign not recorded"); + break; + case BOTH: + if (! BE_VERBOSE) + fail (data, "-mstackrealign only partially used. Run with -v to list"); + else + fail (data, "-mstackrealign only partially used."); + break; + case NO: + fail (data, "Compiled without -mstackrealign"); + break; + case YES: + pass (data, "Compiled wit -mstackrealign"); + break; + default: + ice (data, "-mstackrealign notes are incorrect"); + break; + } + } + + if (num_fails == num_maybes && num_fails == 0) + { + einfo (INFO, "%s: PASS", data->filename); + return true; + } + else if (num_fails > 0) + return false; + else /* FIXME: Add an option to ignore MAYBE results... */ + return false; +} + +static void +version (void) +{ + einfo (INFO, "version 1.0"); +} + +static void +usage (void) +{ + einfo (INFO, "Hardening/Security checker. Tests for:"); + einfo (INFO, " lazy binding"); + einfo (INFO, " executable stack"); + einfo (INFO, " segments with write + executable"); + einfo (INFO, " text relocations"); + einfo (INFO, " runpath entries not under /usr"); + einfo (INFO, " missing annobin data"); + einfo (INFO, " missing dynamic segment"); + einfo (INFO, " writeable relocations for the GOT"); + einfo (INFO, " compilation without sufficient optimization"); + einfo (INFO, " compilation without -fstack-protector-strong"); + einfo (INFO, " compilation without -D_FORTIFY_SOURCE=2"); + einfo (INFO, " compilation without -D_GLIBCXX_ASSERTIONS"); + einfo (INFO, " compilation without -fPIE"); + einfo (INFO, " compilation without -fstack-clash-protection (not arm)"); + einfo (INFO, " compilation without -fexceptions"); + einfo (INFO, " compilation without -fcf-protection=full (x86 only, gcc 8 only)"); + einfo (INFO, " compilation without -mstackrealign (i686 only)"); + einfo (INFO, "Still to do:"); + einfo (INFO, " Add a machine readable output mode"); + einfo (INFO, "This tool is enabled by default. This can be changed by:"); + einfo (INFO, " --disable-hardened Disables the hardening checker"); + einfo (INFO, " --enable-hardened Reenables the hardening checker"); +} + +static bool +process_arg (const char * arg, const char ** argv, const uint argc, uint * next) +{ + const char * parameter; + + if (streq (arg, "--enable-hardened")) + { + disabled = false; + return true; + } + + if (streq (arg, "--disable-hardened")) + { + disabled = true; + return true; + } + + return false; +} + + +struct checker hardened_checker = +{ + "Hardened", + start, + interesting_sec, + check_sec, + interesting_seg, + NULL, /* check_seg */ + finish, + process_arg, + usage, + version, + NULL, /* internal */ +}; + +static __attribute__((constructor)) void +register_checker (void) +{ + if (! eu_checksec_add_checker (& hardened_checker, major_version)) + disabled = true; +} diff --git a/configure b/configure index 89746bc..0a02087 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.64 for Binary Annotations 3.1. +# Generated by GNU Autoconf 2.64 for Binary Annotations 6.1. # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software @@ -556,8 +556,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='Binary Annotations' PACKAGE_TARNAME='annobin-plugin' -PACKAGE_VERSION='3.1' -PACKAGE_STRING='Binary Annotations 3.1' +PACKAGE_VERSION='6.1' +PACKAGE_STRING='Binary Annotations 6.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1298,7 +1298,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures Binary Annotations 3.1 to adapt to many kinds of systems. +\`configure' configures Binary Annotations 6.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1369,7 +1369,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of Binary Annotations 3.1:";; + short | recursive ) echo "Configuration of Binary Annotations 6.1:";; esac cat <<\_ACEOF @@ -1478,7 +1478,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -Binary Annotations configure 3.1 +Binary Annotations configure 6.1 generated by GNU Autoconf 2.64 Copyright (C) 2009 Free Software Foundation, Inc. @@ -2087,7 +2087,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by Binary Annotations $as_me 3.1, which was +It was created by Binary Annotations $as_me 6.1, which was generated by GNU Autoconf 2.64. Invocation command line was $ $0 $@ @@ -4350,7 +4350,7 @@ fi # Define the identity of the package. PACKAGE='annobin-plugin' - VERSION='3.1' + VERSION='6.1' # Some tools Automake needs. @@ -14752,7 +14752,7 @@ Usage: $0 [OPTIONS] Report bugs to ." lt_cl_version="\ -Binary Annotations config.lt 3.1 +Binary Annotations config.lt 6.1 configured by $0, generated by GNU Autoconf 2.64. Copyright (C) 2009 Free Software Foundation, Inc. @@ -16406,7 +16406,7 @@ $as_echo "#define HAVE_SYS_WAIT_H 1" >>confdefs.h fi -ac_config_files="$ac_config_files scripts/Makefile plugin/Makefile doc/Makefile Makefile tests/Makefile" +ac_config_files="$ac_config_files scripts/Makefile plugin/Makefile doc/Makefile Makefile tests/Makefile annocheck/Makefile" ac_config_headers="$ac_config_headers plugin/config.h" @@ -16935,7 +16935,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by Binary Annotations $as_me 3.1, which was +This file was extended by Binary Annotations $as_me 6.1, which was generated by GNU Autoconf 2.64. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -16999,7 +16999,7 @@ Report bugs to the package provider." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -Binary Annotations config.status 3.1 +Binary Annotations config.status 6.1 configured by $0, generated by GNU Autoconf 2.64, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" @@ -17490,6 +17490,7 @@ do "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "tests/Makefile") CONFIG_FILES="$CONFIG_FILES tests/Makefile" ;; + "annocheck/Makefile") CONFIG_FILES="$CONFIG_FILES annocheck/Makefile" ;; "plugin/config.h") CONFIG_HEADERS="$CONFIG_HEADERS plugin/config.h" ;; *) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;; diff --git a/configure.ac b/configure.ac index 25a87fe..8e0a320 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ # license : GNU GPL v3; see accompanying LICENSE file. AC_PREREQ(2.64) -AC_INIT([Binary Annotations], 3.1,,[annobin-plugin]) +AC_INIT([Binary Annotations], 6.1,,[annobin-plugin]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_SRCDIR([plugin/annobin.h]) @@ -121,6 +121,6 @@ AC_SUBST(target_noncanonical) AC_TYPE_INT64_T AC_TYPE_UINT64_T AC_HEADER_SYS_WAIT -AC_CONFIG_FILES(scripts/Makefile plugin/Makefile doc/Makefile Makefile tests/Makefile) +AC_CONFIG_FILES(scripts/Makefile plugin/Makefile doc/Makefile Makefile tests/Makefile annocheck/Makefile) AC_CONFIG_HEADERS(plugin/config.h) AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am index 3cca684..d172097 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -14,7 +14,7 @@ info_TEXINFOS = annobin.texi annobin_TEXINFOS = fdl.texi # Man page creation. -man_MANS = annobin.1 built-by.1 check-abi.1 hardened.1 run-on-binaries.1 +man_MANS = annobin.1 built-by.1 check-abi.1 hardened.1 run-on-binaries.1 annocheck.1 MANCONF = -Dman @@ -29,6 +29,13 @@ annobin.1: $(srcdir)/$(info_TEXINFOS) mv -f $@.T$$$$ $@) || (rm -f $@.T$$$$ && exit 1) rm -f annobin.pod +annocheck.1: $(srcdir)/$(info_TEXINFOS) + touch $@ + -$(TEXI2POD) $(MANCONF) -Dannocheck < $(srcdir)/$(info_TEXINFOS) > annocheck.pod + -($(POD2MAN) annocheck.pod | sed -e '/^.if n .na/d' > $@.T$$$$ && \ + mv -f $@.T$$$$ $@) || (rm -f $@.T$$$$ && exit 1) + rm -f annocheck.pod + built-by.1: $(srcdir)/$(info_TEXINFOS) touch $@ -$(TEXI2POD) $(MANCONF) -Dbuilt-by < $(srcdir)/$(info_TEXINFOS) > built-by.pod diff --git a/doc/Makefile.in b/doc/Makefile.in index d23d534..d68e257 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -266,7 +266,7 @@ info_TEXINFOS = annobin.texi annobin_TEXINFOS = fdl.texi # Man page creation. -man_MANS = annobin.1 built-by.1 check-abi.1 hardened.1 run-on-binaries.1 +man_MANS = annobin.1 built-by.1 check-abi.1 hardened.1 run-on-binaries.1 annocheck.1 MANCONF = -Dman TEXI2POD = perl $(srcdir)/texi2pod.pl $(AM_MAKEINFOFLAGS) POD2MAN = pod2man --center="RPM Development Tools" --release="annobin-1" --section=1 @@ -751,6 +751,13 @@ annobin.1: $(srcdir)/$(info_TEXINFOS) mv -f $@.T$$$$ $@) || (rm -f $@.T$$$$ && exit 1) rm -f annobin.pod +annocheck.1: $(srcdir)/$(info_TEXINFOS) + touch $@ + -$(TEXI2POD) $(MANCONF) -Dannocheck < $(srcdir)/$(info_TEXINFOS) > annocheck.pod + -($(POD2MAN) annocheck.pod | sed -e '/^.if n .na/d' > $@.T$$$$ && \ + mv -f $@.T$$$$ $@) || (rm -f $@.T$$$$ && exit 1) + rm -f annocheck.pod + built-by.1: $(srcdir)/$(info_TEXINFOS) touch $@ -$(TEXI2POD) $(MANCONF) -Dbuilt-by < $(srcdir)/$(info_TEXINFOS) > built-by.pod diff --git a/doc/annobin.1 b/doc/annobin.1 index a31efdb..a8da396 100644 --- a/doc/annobin.1 +++ b/doc/annobin.1 @@ -129,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "ANNOBIN 1" -.TH ANNOBIN 1 "2018-05-31" "annobin-1" "RPM Development Tools" +.TH ANNOBIN 1 "2018-06-06" "annobin-1" "RPM Development Tools" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -172,10 +172,10 @@ from the compiler to record details such as the \s-1ABI\s0 variant in use and the hardening options applied. .PP The information can be extracted from files via the use of tools like -\&\f(CW\*(C`readelf\*(C'\fR. The \f(CW\*(C`annobin\*(C'\fR package includes a set of sample -scripts that use \f(CW\*(C`readelf\*(C'\fR in this way to check various features -of an application, such as its conformation to the hardening -requirements, or possible \s-1ABI\s0 violations. +\&\f(CW\*(C`readelf\*(C'\fR. The \f(CW\*(C`annobin\*(C'\fR package includes a program called +\&\fBannocheck\fR which can check various features of an +application, such as its conformation to the hardening requirements, +or possible \s-1ABI\s0 violations. .PP Normally the option to enable the recording of binary annotation notes is enabled automatically by the build system, so no user intervention @@ -185,8 +185,8 @@ is required. On Fedora and \s-1RHEL\s0 based systems this is handled by the Currently the binary annotations are generated by a plugin to the \&\f(CW\*(C`GCC\*(C'\fR compiler called \fBannobin\fR. This does mean that files that are not compiled with \f(CW\*(C`GCC\*(C'\fR will not gain any binary -annotations. Solving this problem is one of the future goals of the -annobin project. +annotations, although there is an optional assembler switch to add +some basic notes if none are present in the input files. .PP If the build system being used does not automatically enabled the \&\fBannobin\fR plugin then it can be specifically added to the gcc diff --git a/doc/annobin.info b/doc/annobin.info index a3be856..bd74b18 100644 --- a/doc/annobin.info +++ b/doc/annobin.info @@ -14,7 +14,7 @@ Free Documentation License". INFO-DIR-SECTION Software development START-INFO-DIR-ENTRY -* annobin: (annobin). Annotating your program's compilation +* annobin: (annobin). Annotating a compiled program END-INFO-DIR-ENTRY  @@ -23,10 +23,11 @@ File: annobin.info, Node: Top, Next: Introduction, Up: (dir) Annotating Binaries: How Was Your Program Built ? ************************************************* -This manual describes the ANNOBIN plugin, and how you can use it to -determine what security features were used when building your binary. +This manual describes the ANNOBIN plugin and the 'annocheck' program, +and how you can use them to determine what security features were used +when a program was built. - This manual is for 'annobin' (Annobin) version 5.9. + This manual is for 'annobin' (Annobin) version 6.0. This document is distributed under the terms of the GNU Free Documentation License version 1.3. A copy of the license is included in @@ -36,8 +37,9 @@ the section entitled "GNU Free Documentation License". * Introduction:: What is Binary Annotation ? * Invocation:: How to add Binary Annotations to your application. -* Checking:: How to examine the information stored in the binary. -* Using:: How to use the information stored in the binary. +* Examining:: How to examine the information stored in the binary. +* Annocheck:: Analyzing your applications's compilation. +* Legacy Scripts:: Other ways to use the information stored in the binary. * GNU FDL:: GNU Free Documentation License  @@ -78,13 +80,12 @@ compiler to record details such as the ABI variant in use and the hardening options applied. The information can be extracted from files via the use of tools like -'readelf'. The 'annobin' package includes a set of sample scripts that -use 'readelf' in this way to check various features of an application, -such as its conformation to the hardening requirements, or possible ABI -violations. +'readelf'. The 'annobin' package includes a program called 'annocheck' +which can check various features of an application, such as its +conformation to the hardening requirements, or possible ABI violations.  -File: annobin.info, Node: Invocation, Next: Checking, Prev: Introduction, Up: Top +File: annobin.info, Node: Invocation, Next: Examining, Prev: Introduction, Up: Top 2 How to add Binary Annotations to your application. **************************************************** @@ -96,8 +97,9 @@ required. On Fedora and RHEL based systems this is handled by the Currently the binary annotations are generated by a plugin to the 'GCC' compiler called 'annobin'. This does mean that files that are not -compiled with 'GCC' will not gain any binary annotations. Solving this -problem is one of the future goals of the annobin project. +compiled with 'GCC' will not gain any binary annotations, although there +is an optional assembler switch to add some basic notes if none are +present in the input files. If the build system being used does not automatically enabled the 'annobin' plugin then it can be specifically added to the gcc command @@ -181,7 +183,7 @@ after the plugin itself is mentioned. The options are: the 'no-global-file-syms' option.  -File: annobin.info, Node: Checking, Next: Using, Prev: Invocation, Up: Top +File: annobin.info, Node: Examining, Next: Annocheck, Prev: Invocation, Up: Top 3 How to examine the information stored in the binary. ****************************************************** @@ -215,8 +217,8 @@ and 'A' followed by an encoding character (one of '*$!+') and then a type character and finally the value. Most of the notes have a reasonably self explanatory name and value. -The exception are the 'version' and 'GOW' notes, which are explained -below. +The exception are the 'version' and 'GOW' notes, which are included in +the table below. * Menu: @@ -229,7 +231,7 @@ below. * The ENUM Encoding:: Encoding the Size of Enumerations  -File: annobin.info, Node: The Version Encoding, Next: The STACK Encoding, Up: Checking +File: annobin.info, Node: The Version Encoding, Next: The STACK Encoding, Up: Examining 3.1 Encoding Protocol and Producer Versions =========================================== @@ -239,7 +241,7 @@ used and the version of the tool used to generate the notes. Typically the protocol version will be 3 and the plugin version will be 5.  -File: annobin.info, Node: The STACK Encoding, Next: The PIC Encoding, Prev: The Version Encoding, Up: Checking +File: annobin.info, Node: The STACK Encoding, Next: The PIC Encoding, Prev: The Version Encoding, Up: Examining 3.2 Encoding Stack Protections ============================== @@ -264,7 +266,7 @@ The stack protection note (value 2) encodes the setting of the Compiled with '-fstack-protector-explicit'.  -File: annobin.info, Node: The PIC Encoding, Next: The GOW Encoding, Prev: The STACK Encoding, Up: Checking +File: annobin.info, Node: The PIC Encoding, Next: The GOW Encoding, Prev: The STACK Encoding, Up: Examining 3.3 Encoding Position Independence ================================== @@ -292,7 +294,7 @@ can be 'pie' takes the precedence in the encoding.  -File: annobin.info, Node: The GOW Encoding, Next: The CF Encoding, Prev: The PIC Encoding, Up: Checking +File: annobin.info, Node: The GOW Encoding, Next: The CF Encoding, Prev: The PIC Encoding, Up: Examining 3.4 Encoding Optimization and Debugging Levels ============================================== @@ -337,7 +339,7 @@ stored as a bit field with the bits having the following meanings: they can be used in future extensions to the specification.  -File: annobin.info, Node: The CF Encoding, Next: The CET Encoding, Prev: The GOW Encoding, Up: Checking +File: annobin.info, Node: The CF Encoding, Next: The CET Encoding, Prev: The GOW Encoding, Up: Examining 3.5 Encoding Control Flow Protection ==================================== @@ -366,7 +368,7 @@ be confused with a NUL-byte to indicate the end of a string), the value stored is biased by 1.  -File: annobin.info, Node: The CET Encoding, Next: The ENUM Encoding, Prev: The CF Encoding, Up: Checking +File: annobin.info, Node: The CET Encoding, Next: The ENUM Encoding, Prev: The CF Encoding, Up: Examining 3.6 Encoding Control Flow Enforcement Technology ================================================ @@ -392,7 +394,7 @@ of bytes that indicate various different flags: 2 (set).  -File: annobin.info, Node: The ENUM Encoding, Prev: The CET Encoding, Up: Checking +File: annobin.info, Node: The ENUM Encoding, Prev: The CET Encoding, Up: Examining 3.7 Encoding the Size of Enumerations ===================================== @@ -406,13 +408,178 @@ Record the value of the '-fshort-enums' option. Possible values are: The '-fshort-enums' option has not been enabled.  -File: annobin.info, Node: Using, Next: GNU FDL, Prev: Checking, Up: Top +File: annobin.info, Node: Annocheck, Next: Legacy Scripts, Prev: Examining, Up: Top -4 How to use the information stored in the binary. +4 Analyzing an application's compilation. +***************************************** + + annocheck + [-help] + [-version] + [-verbose] + [-quiet] + [-ignore-unknown] + [-dwarf-dir=DIR] + [-prefix=TEXT] + FILE... + + The 'annocheck' program can analyze programs and report information +about them. It is designed to be modular, with a set of self-contained +tools providing the checking functionality. Currently the following +tools are implemented: + +* Menu: + +* Hardened:: Was the application built with the correct security options ? +* Built-By:: Which tool(s) were used to build the application ? + + The 'annocheck' program is able to scan inside rpm files and +libraries. It will automatically recurse into any directories that are +specified on the command line. In addition 'annocheck' knows how to +find debug information held in separate debug files, and it will search +for these whenever it is checking debug information. + + The program supports some generic command line options that are used +regardless of which tools are enabled. + +'--dwarf-dir=DIR' + Look in DIR for separate dwarf debug information files + +'--help' + Display this message & exit. + +'--ignore-unknown' + Do not complain about unknown file types. + +'--prefix=TEXT' + Include TEXT in the output description. + +'--quiet' + Do not print anything, just return an exit status. + +'--verbose' + Produce informational messages whilst working. Repeat for more + information. + +'--version' + Report the verion of the tool and then exit. + + +File: annobin.info, Node: Hardened, Next: Built-By, Up: Annocheck + +4.1 The security checker. +========================= + +This tool checks that the specified files were compiled with the +required security hardening options, as outlined in the elf-policy +document. Currently the tool checks for these features: + +'BIND_NOW' + Lazy binding must have been enabled via the linker option '-z now'. + +'Non executable stack' + The program must not have a stack in an executable region of + memory. + +'Safe GOT relocations' + The relocations for the GOT table must be read only. + +'No WX segments.' + No program segment should have all three of the read, write and + execute permission bits set. + +'No text relocations' + The should be no relocations agains executable code. + +'Correct runpaths' + The runpath information used to locate shared libraries at runtime + must only include directories rooted at /USR. + +'Missing annobin data' + The program must have been compiled with annobin notes enabled. + +'Strong stack protection' + The program must have been compiled with the + '-fstack-protector-strong' option enabled, and with + '-D_FORTIFY_SOURCE=2' specified. It must also have been compiled + at at least optimization level 2. + +'Dynamic data present' + Dynamic executables must have a dynamic segment. + +'Position Independent compilation' + Shared libraries must have been compiled with '-fPIE' and dynamic + executables must have been compiled with '-fPIC'. + +'Safe exceptions' + Program which use exception handling must have been compiled with + '-fexceptions' enabled and with '-D_GLIBCXX_ASSERTIONS' specified. + +'Stack Clash protection' + If available the '-fstack-clash-protection' must have been used. + +'Control Flow protection' + If available the '-fcf-protection=full' must have been used. + +'Stack realignment' + For I686 binaries, the '-mstackrealign' option must have been + specified. + + The tool does support a couple of command line options: + +'--enable-hardened' + Enable the tool if it was previously disabled. + +'--disable-hardened' + Disable the tool. + + +File: annobin.info, Node: Built-By, Prev: Hardened, Up: Annocheck + +4.2 The builder checker. +======================== + +The BUILT-BY tool is disabled by default, but it can be enabled by the +command line option '--enable-builtby'. The tool checks the specified +files to see if any information is stored about how the file was built. + + The tool supports a few command line options to customise its +behaviour: + +'--all' + Report all builder identification strings. The tool has several + different heuristics for determining the builder. By default it + will report the information return by the first successful + heuristic. If the '--all' option is enabled then all successful + results will be returned. + +'--tool=NAME' + This option can be used to restict the output to only those files + which were built by a specific tool. This can be useful when + scanning a directory full of files searching for those built by a + particular compiler. + +'--nottool=NAME' + This option can be used to restict the output to only those files + which were not built by a specific tool. This can be useful when + scanning a directory full of files searching for those that were + not built by a particular compiler. + + +File: annobin.info, Node: Legacy Scripts, Next: GNU FDL, Prev: Annocheck, Up: Top + +5 How to use the information stored in the binary. ************************************************** The 'annobin' package includes some example scripts that demonstrate how -the binary information can be used. The scripts are: +the binary information can be used. + + _NOTE_: These scripts are now redundant, their functionality having +been sussumed into the 'annocheck' program. However they are still +useful as examples of how the annobin data can be consumed, so they are +still included in the annobin sources. + + The scripts are: * Menu: @@ -422,9 +589,9 @@ the binary information can be used. The scripts are: * Checking Archives:: The run-on-binaries-in script  -File: annobin.info, Node: Who Built Me, Next: ABI Checking, Up: Using +File: annobin.info, Node: Who Built Me, Next: ABI Checking, Up: Legacy Scripts -4.1 The built-by script +5.1 The built-by script ======================= built-by @@ -524,9 +691,9 @@ version of a tool that was built between a range of dates. be run on files whose names starts with a dash.  -File: annobin.info, Node: ABI Checking, Next: Hardening Checks, Prev: Who Built Me, Up: Using +File: annobin.info, Node: ABI Checking, Next: Hardening Checks, Prev: Who Built Me, Up: Legacy Scripts -4.2 The check-abi script +5.2 The check-abi script ======================== check-abi @@ -609,9 +776,9 @@ used uniformly throughout the binary. be run on files whose names starts with a dash.  -File: annobin.info, Node: Hardening Checks, Next: Checking Archives, Prev: ABI Checking, Up: Using +File: annobin.info, Node: Hardening Checks, Next: Checking Archives, Prev: ABI Checking, Up: Legacy Scripts -4.3 The hardened script +5.3 The hardened script ======================= hardened @@ -756,9 +923,9 @@ compiled with '-O2' or higher and the '-fstack-protector-strong', be run on files whose names starts with a dash.  -File: annobin.info, Node: Checking Archives, Prev: Hardening Checks, Up: Using +File: annobin.info, Node: Checking Archives, Prev: Hardening Checks, Up: Legacy Scripts -4.4 The run-on-binaries-in script +5.4 The run-on-binaries-in script ================================= run-on-binaries-in @@ -844,7 +1011,7 @@ by the 'find' command, like this: to be run with a program whoes name starts with a dash.  -File: annobin.info, Node: GNU FDL, Prev: Using, Up: Top +File: annobin.info, Node: GNU FDL, Prev: Legacy Scripts, Up: Top Appendix A GNU Free Documentation License ***************************************** @@ -1328,22 +1495,25 @@ their use in free software.  Tag Table: -Node: Top706 -Node: Introduction1584 -Node: Invocation3686 -Node: Checking7883 -Node: The Version Encoding10339 -Node: The STACK Encoding10729 -Node: The PIC Encoding11326 -Node: The GOW Encoding11953 -Node: The CF Encoding13400 -Node: The CET Encoding14470 -Node: The ENUM Encoding15264 -Node: Using15621 -Node: Who Built Me16124 -Node: ABI Checking18875 -Node: Hardening Checks20980 -Node: Checking Archives25057 -Node: GNU FDL27470 +Node: Top698 +Node: Introduction1675 +Node: Invocation3758 +Node: Examining7994 +Node: The Version Encoding10467 +Node: The STACK Encoding10858 +Node: The PIC Encoding11456 +Node: The GOW Encoding12084 +Node: The CF Encoding13532 +Node: The CET Encoding14603 +Node: The ENUM Encoding15398 +Node: Annocheck15756 +Node: Hardened17413 +Node: Built-By19554 +Node: Legacy Scripts20804 +Node: Who Built Me21571 +Node: ABI Checking24331 +Node: Hardening Checks26445 +Node: Checking Archives30531 +Node: GNU FDL32953  End Tag Table diff --git a/doc/annobin.texi b/doc/annobin.texi index 268c688..887f605 100644 --- a/doc/annobin.texi +++ b/doc/annobin.texi @@ -5,9 +5,9 @@ @setchapternewpage odd @c man begin INCLUDE -@set VERSION 5.9 +@set VERSION 6.0 @set VERSION_PACKAGE (Annobin) -@set UPDATED May 2018 +@set UPDATED June 2018 @c man end @ifnottex @@ -15,7 +15,7 @@ @c manuals to an info tree. zoo@cygnus.com is developing this facility. @dircategory Software development @direntry -* annobin: (annobin). Annotating your program's compilation +* annobin: (annobin). Annotating a compiled program @end direntry @end ifnottex @@ -49,8 +49,9 @@ section entitled ``GNU Free Documentation License''. @page -This manual describes the @sc{annobin} plugin, and how you can use it -to determine what security features were used when building your binary. +This manual describes the @sc{annobin} plugin and the +@command{annocheck} program, and how you can use them to determine +what security features were used when a program was built. @vskip 0pt plus 1filll Copyright @copyright{} 2018 Red Hat @@ -72,8 +73,9 @@ Copyright @copyright{} 2018 Red Hat @c man annobin title Annobin - annotating binary files. -This manual describes the @sc{annobin} plugin, and how you can use it -to determine what security features were used when building your binary. +This manual describes the @sc{annobin} plugin and the +@command{annocheck} program, and how you can use them to determine +what security features were used when a program was built. This manual is for @code{annobin} @ifset VERSION_PACKAGE @@ -88,8 +90,9 @@ in the section entitled ``GNU Free Documentation License''. @menu * Introduction:: What is Binary Annotation ? * Invocation:: How to add Binary Annotations to your application. -* Checking:: How to examine the information stored in the binary. -* Using:: How to use the information stored in the binary. +* Examining:: How to examine the information stored in the binary. +* Annocheck:: Analyzing your applications's compilation. +* Legacy Scripts:: Other ways to use the information stored in the binary. * GNU FDL:: GNU Free Documentation License @end menu @end ifnottex @@ -132,10 +135,10 @@ from the compiler to record details such as the ABI variant in use and the hardening options applied. The information can be extracted from files via the use of tools like -@code{readelf}. The @code{annobin} package includes a set of sample -scripts that use @code{readelf} in this way to check various features -of an application, such as its conformation to the hardening -requirements, or possible ABI violations. +@code{readelf}. The @code{annobin} package includes a program called +@command{annocheck} which can check various features of an +application, such as its conformation to the hardening requirements, +or possible ABI violations. @c man end @@ -153,8 +156,8 @@ is required. On Fedora and RHEL based systems this is handled by the Currently the binary annotations are generated by a plugin to the @code{GCC} compiler called @samp{annobin}. This does mean that files that are not compiled with @code{GCC} will not gain any binary -annotations. Solving this problem is one of the future goals of the -annobin project. +annotations, although there is an optional assembler switch to add +some basic notes if none are present in the input files. If the build system being used does not automatically enabled the @samp{annobin} plugin then it can be specifically added to the gcc @@ -244,7 +247,7 @@ by default, and if enabled can be disabled again via the @c man end @c ----------------------------------------------------------------- -@node Checking +@node Examining @chapter How to examine the information stored in the binary. @c man begin DESCRIPTION annobin @@ -283,7 +286,7 @@ the Watermark specification, but it basically consists of the letters Most of the notes have a reasonably self explanatory name and value. The exception are the @code{version} and @code{GOW} notes, which are -explained below. +included in the table below. @menu * The Version Encoding:: Encoding Versions @@ -473,11 +476,204 @@ The @option{-fshort-enums} option has not been enabled. @end table @c ----------------------------------------------------------------- -@node Using +@node Annocheck +@chapter Analyzing an application's compilation. + +@c man title annocheck Analyzing an application's compilation. + +@smallexample +@c man begin SYNOPSIS annocheck +annocheck + [@b{--help}] + [@b{--version}] + [@b{--verbose}] + [@b{--quiet}] + [@b{--ignore-unknown}] + [@b{--dwarf-dir=}@var{dir}] + [@b{--prefix=}@var{text}] + @var{file}@dots{} +@c man end +@end smallexample + +@c man begin DESCRIPTION annocheck + +The @command{annocheck} program can analyze programs and report +information about them. It is designed to be modular, with a set of +self-contained tools providing the checking functionality. +Currently the following tools are implemented: + +@menu +* Hardened:: Was the application built with the correct security options ? +* Built-By:: Which tool(s) were used to build the application ? +@end menu + +The @command{annocheck} program is able to scan inside rpm files and +libraries. It will automatically recurse into any directories that +are specified on the command line. In addition @command{annocheck} +knows how to find debug information held in separate debug files, and +it will search for these whenever it is checking debug information. + +The program supports some generic command line options that are used +regardless of which tools are enabled. + +@table @code + +@item --dwarf-dir=@var{dir} +Look in @var{dir} for separate dwarf debug information files + +@item --help +Display this message & exit. + +@item --ignore-unknown +Do not complain about unknown file types. + +@item --prefix=@var{text} +Include @var{text} in the output description. + +@item --quiet +Do not print anything, just return an exit status. + +@item --verbose +Produce informational messages whilst working. Repeat for more +information. + +@item --version +Report the verion of the tool and then exit. + +@end table + +@c man end + +@c ----------------------------------------------------------------- +@node Hardened +@section The security checker. + +@c man begin DESCRIPTION annocheck +This tool checks that the specified files were compiled with the +required security hardening options, as outlined in the elf-policy +document. Currently the tool checks for these features: + +@table @code + +@item BIND_NOW +Lazy binding must have been enabled via the linker option @option{-z +now}. + +@item Non executable stack +The program must not have a stack in an executable region of memory. + +@item Safe GOT relocations +The relocations for the GOT table must be read only. + +@item No WX segments. +No program segment should have all three of the read, write and +execute permission bits set. + +@item No text relocations +The should be no relocations agains executable code. + +@item Correct runpaths +The runpath information used to locate shared libraries at runtime +must only include directories rooted at @var{/usr}. + +@item Missing annobin data +The program must have been compiled with annobin notes enabled. + +@item Strong stack protection +The program must have been compiled with the @option +{-fstack-protector-strong} option enabled, and with +@option{-D_FORTIFY_SOURCE=2} specified. It must also have been +compiled at at least optimization level 2. + +@item Dynamic data present +Dynamic executables must have a dynamic segment. + +@item Position Independent compilation +Shared libraries must have been compiled with @option{-fPIE} and +dynamic executables must have been compiled with @option{-fPIC}. + +@item Safe exceptions +Program which use exception handling must have been compiled with +@option{-fexceptions} enabled and with @option{-D_GLIBCXX_ASSERTIONS} +specified. + +@item Stack Clash protection +If available the @option {-fstack-clash-protection} must have been +used. + +@item Control Flow protection +If available the @option{-fcf-protection=full} must have been used. + +@item Stack realignment +For @var{i686} binaries, the @option{-mstackrealign} option must have +been specified. + +@end table + +The tool does support a couple of command line options: + +@table @code +@item --enable-hardened +Enable the tool if it was previously disabled. + +@item --disable-hardened +Disable the tool. +@end table + +@c man end + +@c ----------------------------------------------------------------- +@node Built-By +@section The builder checker. + +@c man begin DESCRIPTION annocheck + +The @var{built-by} tool is disabled by default, but it can be enabled +by the command line option @option{--enable-builtby}. The tool +checks the specified files to see if any information is stored about +how the file was built. + +The tool supports a few command line options to customise its +behaviour: + +@table @code + +@item --all +Report all builder identification strings. The tool has several +different heuristics for determining the builder. By default it will +report the information return by the first successful heuristic. If +the @option{--all} option is enabled then all successful results will +be returned. + +@item --tool=@var{name} +This option can be used to restict the output to only those files +which were built by a specific tool. This can be useful when scanning +a directory full of files searching for those built by a particular +compiler. + +@item --nottool=@var{NAME} +This option can be used to restict the output to only those files +which were not built by a specific tool. This can be useful when +scanning a directory full of files searching for those that were not +built by a particular compiler. + +@end table + +@c man end + +@c ----------------------------------------------------------------- +@node Legacy Scripts @chapter How to use the information stored in the binary. The @command{annobin} package includes some example scripts that -demonstrate how the binary information can be used. The scripts are: +demonstrate how the binary information can be used. + +@emph{NOTE}: These scripts are now redundant, their functionality +having been sussumed into the @command{annocheck} program. However +they are still useful as examples of how the annobin data can be +consumed, so they are still included in the annobin sources. + +The scripts are: @menu * Who Built Me:: The built-by script diff --git a/doc/annocheck.1 b/doc/annocheck.1 new file mode 100644 index 0000000..7ca7d97 --- /dev/null +++ b/doc/annocheck.1 @@ -0,0 +1,316 @@ +.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.if !\nF .nr F 0 +.if \nF>0 \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +.\} +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "ANNOCHECK 1" +.TH ANNOCHECK 1 "2018-06-06" "annobin-1" "RPM Development Tools" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +annocheck \- Analyzing an application's compilation. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +annocheck + [\fB\-\-help\fR] + [\fB\-\-version\fR] + [\fB\-\-verbose\fR] + [\fB\-\-quiet\fR] + [\fB\-\-ignore\-unknown\fR] + [\fB\-\-dwarf\-dir=\fR\fIdir\fR] + [\fB\-\-prefix=\fR\fItext\fR] + \fIfile\fR... +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +The \fBannocheck\fR program can analyze programs and report +information about them. It is designed to be modular, with a set of +self-contained tools providing the checking functionality. +Currently the following tools are implemented: +.PP +The \fBannocheck\fR program is able to scan inside rpm files and +libraries. It will automatically recurse into any directories that +are specified on the command line. In addition \fBannocheck\fR +knows how to find debug information held in separate debug files, and +it will search for these whenever it is checking debug information. +.PP +The program supports some generic command line options that are used +regardless of which tools are enabled. +.ie n .IP """\-\-dwarf\-dir=\fIdir\fP""" 4 +.el .IP "\f(CW\-\-dwarf\-dir=\f(CIdir\f(CW\fR" 4 +.IX Item "--dwarf-dir=dir" +Look in \fIdir\fR for separate dwarf debug information files +.ie n .IP """\-\-help""" 4 +.el .IP "\f(CW\-\-help\fR" 4 +.IX Item "--help" +Display this message & exit. +.ie n .IP """\-\-ignore\-unknown""" 4 +.el .IP "\f(CW\-\-ignore\-unknown\fR" 4 +.IX Item "--ignore-unknown" +Do not complain about unknown file types. +.ie n .IP """\-\-prefix=\fItext\fP""" 4 +.el .IP "\f(CW\-\-prefix=\f(CItext\f(CW\fR" 4 +.IX Item "--prefix=text" +Include \fItext\fR in the output description. +.ie n .IP """\-\-quiet""" 4 +.el .IP "\f(CW\-\-quiet\fR" 4 +.IX Item "--quiet" +Do not print anything, just return an exit status. +.ie n .IP """\-\-verbose""" 4 +.el .IP "\f(CW\-\-verbose\fR" 4 +.IX Item "--verbose" +Produce informational messages whilst working. Repeat for more +information. +.ie n .IP """\-\-version""" 4 +.el .IP "\f(CW\-\-version\fR" 4 +.IX Item "--version" +Report the verion of the tool and then exit. +.PP +This tool checks that the specified files were compiled with the +required security hardening options, as outlined in the elf-policy +document. Currently the tool checks for these features: +.ie n .IP """BIND_NOW""" 4 +.el .IP "\f(CWBIND_NOW\fR" 4 +.IX Item "BIND_NOW" +Lazy binding must have been enabled via the linker option \fB\-z +now\fR. +.ie n .IP """Non executable stack""" 4 +.el .IP "\f(CWNon executable stack\fR" 4 +.IX Item "Non executable stack" +The program must not have a stack in an executable region of memory. +.ie n .IP """Safe GOT relocations""" 4 +.el .IP "\f(CWSafe GOT relocations\fR" 4 +.IX Item "Safe GOT relocations" +The relocations for the \s-1GOT\s0 table must be read only. +.ie n .IP """No WX segments.""" 4 +.el .IP "\f(CWNo WX segments.\fR" 4 +.IX Item "No WX segments." +No program segment should have all three of the read, write and +execute permission bits set. +.ie n .IP """No text relocations""" 4 +.el .IP "\f(CWNo text relocations\fR" 4 +.IX Item "No text relocations" +The should be no relocations agains executable code. +.ie n .IP """Correct runpaths""" 4 +.el .IP "\f(CWCorrect runpaths\fR" 4 +.IX Item "Correct runpaths" +The runpath information used to locate shared libraries at runtime +must only include directories rooted at \fI/usr\fR. +.ie n .IP """Missing annobin data""" 4 +.el .IP "\f(CWMissing annobin data\fR" 4 +.IX Item "Missing annobin data" +The program must have been compiled with annobin notes enabled. +.ie n .IP """Strong stack protection""" 4 +.el .IP "\f(CWStrong stack protection\fR" 4 +.IX Item "Strong stack protection" +The program must have been compiled with the \f(CW@option\fR +{\-fstack\-protector\-strong} option enabled, and with +\&\fB\-D_FORTIFY_SOURCE=2\fR specified. It must also have been +compiled at at least optimization level 2. +.ie n .IP """Dynamic data present""" 4 +.el .IP "\f(CWDynamic data present\fR" 4 +.IX Item "Dynamic data present" +Dynamic executables must have a dynamic segment. +.ie n .IP """Position Independent compilation""" 4 +.el .IP "\f(CWPosition Independent compilation\fR" 4 +.IX Item "Position Independent compilation" +Shared libraries must have been compiled with \fB\-fPIE\fR and +dynamic executables must have been compiled with \fB\-fPIC\fR. +.ie n .IP """Safe exceptions""" 4 +.el .IP "\f(CWSafe exceptions\fR" 4 +.IX Item "Safe exceptions" +Program which use exception handling must have been compiled with +\&\fB\-fexceptions\fR enabled and with \fB\-D_GLIBCXX_ASSERTIONS\fR +specified. +.ie n .IP """Stack Clash protection""" 4 +.el .IP "\f(CWStack Clash protection\fR" 4 +.IX Item "Stack Clash protection" +If available the \f(CW@option\fR {\-fstack\-clash\-protection} must have been +used. +.ie n .IP """Control Flow protection""" 4 +.el .IP "\f(CWControl Flow protection\fR" 4 +.IX Item "Control Flow protection" +If available the \fB\-fcf\-protection=full\fR must have been used. +.ie n .IP """Stack realignment""" 4 +.el .IP "\f(CWStack realignment\fR" 4 +.IX Item "Stack realignment" +For \fIi686\fR binaries, the \fB\-mstackrealign\fR option must have +been specified. +.PP +The tool does support a couple of command line options: +.ie n .IP """\-\-enable\-hardened""" 4 +.el .IP "\f(CW\-\-enable\-hardened\fR" 4 +.IX Item "--enable-hardened" +Enable the tool if it was previously disabled. +.ie n .IP """\-\-disable\-hardened""" 4 +.el .IP "\f(CW\-\-disable\-hardened\fR" 4 +.IX Item "--disable-hardened" +Disable the tool. +.PP +The \fIbuilt-by\fR tool is disabled by default, but it can be enabled +by the command line option \fB\-\-enable\-builtby\fR. The tool +checks the specified files to see if any information is stored about +how the file was built. +.PP +The tool supports a few command line options to customise its +behaviour: +.ie n .IP """\-\-all""" 4 +.el .IP "\f(CW\-\-all\fR" 4 +.IX Item "--all" +Report all builder identification strings. The tool has several +different heuristics for determining the builder. By default it will +report the information return by the first successful heuristic. If +the \fB\-\-all\fR option is enabled then all successful results will +be returned. +.ie n .IP """\-\-tool=\fIname\fP""" 4 +.el .IP "\f(CW\-\-tool=\f(CIname\f(CW\fR" 4 +.IX Item "--tool=name" +This option can be used to restict the output to only those files +which were built by a specific tool. This can be useful when scanning +a directory full of files searching for those built by a particular +compiler. +.ie n .IP """\-\-nottool=\fINAME\fP""" 4 +.el .IP "\f(CW\-\-nottool=\f(CINAME\f(CW\fR" 4 +.IX Item "--nottool=NAME" +This option can be used to restict the output to only those files +which were not built by a specific tool. This can be useful when +scanning a directory full of files searching for those that were not +built by a particular compiler. +.SH "OPTIONS" +.IX Header "OPTIONS" +.SH "COPYRIGHT" +.IX Header "COPYRIGHT" +Copyright (c) 2018 Red Hat. +.PP +Permission is granted to copy, distribute and/or modify this document +under the terms of the \s-1GNU\s0 Free Documentation License, Version 1.3 +or any later version published by the Free Software Foundation; +with no Invariant Sections, with no Front-Cover Texts, and with no +Back-Cover Texts. A copy of the license is included in the +section entitled \*(L"\s-1GNU\s0 Free Documentation License\*(R". diff --git a/doc/built-by.1 b/doc/built-by.1 index d42955e..525825f 100644 --- a/doc/built-by.1 +++ b/doc/built-by.1 @@ -129,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "BUILT-BY 1" -.TH BUILT-BY 1 "2018-05-31" "annobin-1" "RPM Development Tools" +.TH BUILT-BY 1 "2018-06-06" "annobin-1" "RPM Development Tools" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/doc/check-abi.1 b/doc/check-abi.1 index 129137c..95f23bc 100644 --- a/doc/check-abi.1 +++ b/doc/check-abi.1 @@ -129,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "CHECK-ABI 1" -.TH CHECK-ABI 1 "2018-05-31" "annobin-1" "RPM Development Tools" +.TH CHECK-ABI 1 "2018-06-06" "annobin-1" "RPM Development Tools" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/doc/hardened.1 b/doc/hardened.1 index afe5f83..30cff32 100644 --- a/doc/hardened.1 +++ b/doc/hardened.1 @@ -129,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "HARDENED 1" -.TH HARDENED 1 "2018-05-31" "annobin-1" "RPM Development Tools" +.TH HARDENED 1 "2018-06-06" "annobin-1" "RPM Development Tools" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/doc/run-on-binaries.1 b/doc/run-on-binaries.1 index 33d8ced..b53af3d 100644 --- a/doc/run-on-binaries.1 +++ b/doc/run-on-binaries.1 @@ -129,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "RUN-ON-BINARIES 1" -.TH RUN-ON-BINARIES 1 "2018-05-31" "annobin-1" "RPM Development Tools" +.TH RUN-ON-BINARIES 1 "2018-06-06" "annobin-1" "RPM Development Tools" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/plugin/annobin.cc b/plugin/annobin.cc index 109b087..cd2dff2 100644 --- a/plugin/annobin.cc +++ b/plugin/annobin.cc @@ -391,6 +391,10 @@ annobin_output_note (const char * name, ++ annobin_note_count; } +/* Fills in the DESC1, DESC2 and DESCSZ parameters for a call to annobin_output_note. */ +#define DESC_PARAMETERS(DESC1, DESC2) \ + DESC1, DESC2, (DESC1) == NULL ? 0 : (DESC2 == NULL) ? (annobin_is_64bit ? 8 : 4) : (annobin_is_64bit ? 16 : 8) + void annobin_output_static_note (const char * buffer, unsigned buffer_len, @@ -401,9 +405,7 @@ annobin_output_static_note (const char * buffer, unsigned note_type) { annobin_output_note (buffer, buffer_len, name_is_string, name_description, - start, end, - start == NULL ? 0 : (annobin_is_64bit ? (end == NULL ? 8 : 16) : (end == NULL ? 4: 8)), - true, note_type); + DESC_PARAMETERS (start, end), true, note_type); } void @@ -610,8 +612,7 @@ record_GOW_settings (unsigned int gow, bool local, const char * cname, const cha { annobin_inform (1, "Record a change in -g/-O/-Wall status for %s", cname); annobin_output_note (buffer, i + 1, false, "numeric: -g/-O/-Wall", - aname, aname_end, annobin_is_64bit ? 16 : 8, true, - FUNC); + DESC_PARAMETERS (aname, aname_end), true, FUNC); } else { @@ -848,7 +849,7 @@ record_glibcxx_assertions (bool on) char buffer [128]; unsigned len = sprintf (buffer, "GA%cGLIBCXX_ASSERTIONS", on ? BOOL_T : BOOL_F); - annobin_output_note (buffer, len + 1, false, "_GLIBCXX_ASSERTIONS defined", + annobin_output_note (buffer, len + 1, false, on ? "_GLIBCXX_ASSERTIONS defined" : "_GLIBCXX_ASSERTIONS not defined", NULL, NULL, 0, false, OPEN); annobin_inform (1, "Record a _GLIBCXX_ASSERTIONS as %s", on ? "defined" : "not defined"); } @@ -1000,9 +1001,9 @@ annobin_create_global_notes (void * gcc_data, void * user_data) record_cf_protection_note (NULL, NULL, OPEN); #endif - /* Look for -D _FORTIFY_SOURCE= on the original gcc command line. - Scan backwards so that we record the last version of the option, - should multiple versions be set. */ + /* Look for -D _FORTIFY_SOURCE= and -D_GLIBCXX_ASSERTIONS on the + original gcc command line. Scan backwards so that we record the + last version of the option, should multiple versions be set. */ bool fortify_level_recorded = false; bool glibcxx_assertions_recorded = false; @@ -1013,6 +1014,8 @@ annobin_create_global_notes (void * gcc_data, void * user_data) if (save_decoded_options[i].arg == NULL) continue; + annobin_inform (2, "decoded arg %s", save_decoded_options[i].arg); + if (strncmp (save_decoded_options[i].arg, "_FORTIFY_SOURCE=", strlen ("_FORTIFY_SOURCE=")) == 0) { int level = atoi (save_decoded_options[i].arg + strlen ("_FORTIFY_SOURCE=")); diff --git a/scripts/check-abi b/scripts/check-abi index 996e8e1..c95f8df 100755 --- a/scripts/check-abi +++ b/scripts/check-abi @@ -24,7 +24,7 @@ # # find . -type f -exec check-abi {} \; -version=3.2 +version=3.3 help () { @@ -64,6 +64,11 @@ Usage: $prog {files|options} -- Stop accumulating options. +The exit code indicates the status: + 0: No FAIl or MAYBE results found + 1: The file could not be parsed or at least one FAIL result found + 2: Some MAYBE results, but no FAIL results + 3: Some other kind of failure __EOM__ exec 1>&4 # Copy stdout fd back from temporary save fd, #4 } @@ -80,7 +85,12 @@ main () then exit 1 else - exit 0 + if [ $maybe -ne 0 ]; + then + exit 2 + else + exit 0 + fi fi } @@ -102,7 +112,7 @@ report () fail () { report "Internal error: " ${1+"$@"} - exit 1 + exit 3 } verbose () @@ -122,6 +132,7 @@ init () num_files=0; failed=0 + maybe=0 silent=0 verb=0 quiet=0 @@ -400,7 +411,8 @@ scan_file () then if [[ $ignore_abi -eq 0 && $inconsistencies -eq 0 ]]; then - report "$file: does not have an ABI note" + report "$file: MAYBE: does not have an ABI note" + maybe=1 fi else if [ ${#abis[*]} -gt 1 ]; @@ -418,7 +430,7 @@ scan_file () if test "${abis[i]}" != "${abis[i-1]}" ; then # FIXME: Add code to differentiate between functions which have changed ABI and files ? - report "$file: differing ABI values detected: ${abis[i]} vs ${abis[i-1]}" + report "$file: FAIL: differing ABI values detected: ${abis[i]} vs ${abis[i-1]}" failed=1 mismatch=1 fi @@ -452,7 +464,8 @@ scan_file () then if [[ $ignore_enum -eq 0 && $inconsistencies -eq 0 ]]; then - report "$file: does not record enum size" + report "$file: MAYBE: does not record enum size" + maybe=1 fi else if [ ${#abis[*]} -gt 1 ]; @@ -468,7 +481,7 @@ scan_file () do if test "${abis[i]}" != "${abis[i-1]}" ; then - report "$file: differing -fshort-enums detected: ${abis[i]} vs ${abis[i-1]}" + report "$file: FAIL: differing -fshort-enums detected: ${abis[i]} vs ${abis[i-1]}" failed=1 mismatch=1 fi @@ -500,7 +513,8 @@ scan_file () then if [[ $ignore_fortify -eq 0 && $inconsistencies -eq 0 ]]; then - report "$file: does not record _FORTIFY_SOURCE level" + report "$file: MAYBE: does not record _FORTIFY_SOURCE level" + maybe=1 fi else if [ ${#abis[*]} -gt 1 ]; @@ -516,7 +530,7 @@ scan_file () do if test "${abis[i]}" != "${abis[i-1]}" ; then - report "$file: differing FORTIFY SOURCE levels: ${abis[i]} vs ${abis[i-1]}" + report "$file: FAIL: differing FORTIFY SOURCE levels: ${abis[i]} vs ${abis[i-1]}" failed=1; mismatch=1; fi @@ -546,7 +560,8 @@ scan_file () then if [[ $ignore_stack_prot -eq 0 && $inconsistencies -eq 0 ]]; then - report "$file: does not record -fstack-protect status" + report "$file: MAYBE: does not record -fstack-protect status" + maybe=1 fi else if [ ${#abis[*]} -eq 1 ]; @@ -564,7 +579,7 @@ scan_file () do if test "${abis[i]}" != "${abis[i-1]}" ; then - report "$file: differing stack protection levels: ${abis[i]} vs ${abis[i-1]}" + report "$file: FAIL: differing stack protection levels: ${abis[i]} vs ${abis[i-1]}" failed=1; mismatch=1; fi diff --git a/scripts/hardened b/scripts/hardened index 77ec3bd..9276a8a 100755 --- a/scripts/hardened +++ b/scripts/hardened @@ -25,11 +25,7 @@ # # find . -type f -exec hardened {} \; -# To Do: -# * Allow arguments to command line options to be separated from the -# the option name by a space. Eg: --readelf foobar - -version=3.2 +version=3.3 help () { @@ -110,6 +106,11 @@ Usage: $prog {files|options} -- Stop accumulating options. Any text that follows this option is assumed to be a file name, even if it starts with a dash. +The exit code indicates the status: + 0: No FAIl or MAYBE results found + 1: The file could not be parsed or at least one FAIL result found + 2: Some MAYBE results, but no FAIL results + 3: Some other kind of failure __EOM__ exec 1>&4 # Copy stdout fd back from temporary save fd, #4 } @@ -126,7 +127,12 @@ main () then exit 1 else - exit 0 + if [ $maybe -ne 0 ]; + then + exit 2 + else + exit 0 + fi fi } @@ -148,7 +154,7 @@ report () ICE () { report "Internal error: " ${1+"$@"} - exit 1 + exit 3 } verbose () @@ -165,8 +171,7 @@ maybe () then report "$file: MAYBE:" ${1+"$@"} fi - - vulnerable=1 + maybe=1 } fail () @@ -175,8 +180,7 @@ fail () then report "$file: FAIL:" ${1+"$@"} fi - - vulnerable=1 + failed=1 } pass () @@ -196,6 +200,7 @@ init () num_files=0; failed=0 + maybe=0 report=1 # Quad-state, 0=> report nothing, 1=> report known vulnerable, 2=> report not proven hardened, 3=> report all verb=0 quiet=0 @@ -454,8 +459,7 @@ scan_file () then if [ $ignore_unknown -eq 0 ]; then - report "$file: file not found" - failed=1 + fail "file not found" fi return fi @@ -464,8 +468,7 @@ scan_file () then if [ $ignore_unknown -eq 0 ]; then - report "$file: not an ordinary file" - failed=1 + fail "not an ordinary file" fi return fi @@ -474,8 +477,7 @@ scan_file () then if [ $ignore_unknown -eq 0 ]; then - report "$file: not readable" - failed=1 + fail "not readable" fi return fi @@ -487,8 +489,7 @@ scan_file () then if [ $ignore_unknown -eq 0 ]; then - report "$file: not an ELF format file" - failed=1 + fail "not an ELF format file" fi return fi @@ -512,8 +513,7 @@ scan_file () $scanner --wide --notes --debug-dump=info --dynamic --segments $file > $tmpfile 2>&1 if [ $? != 0 ]; then - report "scanner '$scanner' failed - see $tmpfile" - failed=1 + fail "scanner '$scanner' failed - see $tmpfile" # Leave the tmpfile intact so that it can be examined by the user. return fi @@ -536,7 +536,6 @@ scan_file () fi local -a hard - local vulnerable=0 if [ $skip_opt -eq 0 ]; then @@ -603,12 +602,6 @@ scan_file () fi fi - # If we found a vulnerable file then consider the check to have failed. - if [ $vulnerable -gt 0 ]; - then - failed=1 - fi - rm -f $tmpfile } diff --git a/tests/assembler-gap-test b/tests/assembler-gap-test index 4185a07..de78097 100755 --- a/tests/assembler-gap-test +++ b/tests/assembler-gap-test @@ -25,7 +25,6 @@ READELF=readelf PLUGIN=../plugin/.libs/annobin.so $GAS --generate-missing-build-notes=yes $srcdir/gap.S -o gap.o -# $GAS --generate-missing-build-notes=no $srcdir/gap.S -o gap.o if [ $? != 0 ]; then echo "Assembler does not support generating build notes. Skipping test." diff --git a/tests/function-sections-test b/tests/function-sections-test index be9c1b5..74700f4 100755 --- a/tests/function-sections-test +++ b/tests/function-sections-test @@ -44,6 +44,12 @@ $GCC -fplugin=$PLUGIN \ -ffunction-sections \ $srcdir/unused_code.c \ -o unused_code.o +if [ $? != 0 ]; +then + echo "$GCC does not support gexplicit stack protection. Skipping test." + exit 0 +fi + $GCC -fplugin=$PLUGIN \ -L . \ diff --git a/tests/hardening-fail-test b/tests/hardening-fail-test index 0320590..2d56255 100755 --- a/tests/hardening-fail-test +++ b/tests/hardening-fail-test @@ -63,5 +63,5 @@ $GCC -fplugin=$PLUGIN \ # $OBJCOPY --merge-notes hardening-fail-test.exe hardening-fail-test-merged.exe -$srcdir/../scripts/hardened --readelf=$READELF --all hardening-fail-test.exe +../annocheck/annocheck hardening-fail-test.exe diff --git a/tests/hardening-test b/tests/hardening-test index 1e109eb..b8ce696 100755 --- a/tests/hardening-test +++ b/tests/hardening-test @@ -20,20 +20,20 @@ OBJCOPY=objcopy PLUGIN=../plugin/.libs/annobin.so -OPTS="-O2 -D_FORTIFY_SOURCE=2 -fpie -Wall -fstack-protector-strong" +OPTS="-c -O2 -D_FORTIFY_SOURCE=2 -fpie -Wall -fstack-protector-strong -D_GLIBCXX_ASSERTIONS -fstack-clash-protection" -$GCC -fplugin=$PLUGIN -c -g $OPTS $srcdir/hello.c +$GCC -fplugin=$PLUGIN -g $OPTS $srcdir/hello_hard.c -$GCC -fplugin=$PLUGIN -O3 -c $OPTS $srcdir/hello2.c +$GCC -fplugin=$PLUGIN -O3 $OPTS $srcdir/hello2.c -$GCC -fplugin=$PLUGIN -c -g3 $OPTS $srcdir/hello3.c +$GCC -fplugin=$PLUGIN -g3 $OPTS $srcdir/hello3.c $GCC -fplugin=$PLUGIN $OPTS -shared $srcdir/hello_lib.c -o libhello.so $GCC -fplugin=$PLUGIN \ -L . -pie \ -Wl,-z,now,-z,relro \ - hello.o hello2.o hello3.o -lhello -o hardening-test.exe + hello_hard.o hello2.o hello3.o -lhello -o hardening-test.exe # $OBJCOPY --merge-notes hardening-test.exe hardening-test-merged.exe @@ -50,7 +50,10 @@ $GCC -fplugin=$PLUGIN \ # is built with -fPIC and -fno-stack-protection. /usr/lib64/Scrti.o is built # with -fpie which makes the pic test impossible. -$srcdir/../scripts/hardened --readelf=$READELF \ - --skip=fort -k=operator --skip=clash --skip=cf -k=cet \ - --skip=pic --skip=stack --skip=realign \ - hardening-test.exe +# $srcdir/../scripts/hardened --readelf=$READELF \ +# --skip=fort -k=operator --skip=clash --skip=cf -k=cet \ +# --skip=pic --skip=stack --skip=realign \ +# hardening-test.exe + +../annocheck/annocheck hardening-test.exe + diff --git a/tests/hello_hard.c b/tests/hello_hard.c new file mode 100644 index 0000000..0b26d52 --- /dev/null +++ b/tests/hello_hard.c @@ -0,0 +1,30 @@ +#include + +extern int big_stack (int); + +int bar (void) __attribute__((optimize("-fstack-protector-strong"),__noinline__)); + +int +ordinary_func (void) +{ + return 77; +} + +int +bar (void) +{ + return 2; +} + +int +baz (void) +{ + return 3; +} + +int +main (void) +{ + return printf ("hello world %d %d %d\n", bar (), baz (), big_stack (3)); +} + -- 2.43.5