Aegis Validator kernel module ----------------------------- Markku Kylänpää 12.11. 2010 Introduction ============ Aegis Validator is a kernel-based integrity protection framework. Userspace applications, shared libraries, certain data files and kernel modules are verified during loading. SHA1 hash of the object to be loaded is calculated in Linux Security Module (LSM) hook function and the result is compared to a stored reference value. The list of the reference values is loaded into kernel during boot. Positive verification results are cached (and indexed by inode number). An object is verified only once after each boot and an attempt to write to a cached entry removes the entry from the cache forcing revalidation. Aegis Platform Security Framework ================================== Aegis Validator is a part of Aegis Platform Security framework, which was published in October 2009 Maemo Summit in Amsterdam. Brief description about concepts is available in: http://www.slideshare.net/peterschneider/maemo-6-platform-security Aegis Validator relies on OMAP Secure Environment services provided by security driver component (Documentation/arm/OMAP/omap_sec.txt) and Aegis Runtime Policy Framework access control mechanism. SHA1 hash calculation is utilizing OMAP SHA1 accelerator. Aegis Validator is a built-in kernel module and there is a securityfs interface to control the module and also to update new reference hash values. Securityfs is a pseudo-filesystem (like sysfs) and is commonly used by security modules to allow the kernel module to interact with userspace. Userspace Debian dpkg installer has been modified to update the reference hash list via securityfs. The initial list is generated and signed by firmware image generation system. Kernel invokes statically linked userspce helper application that loads and verifies the reference hashlist file and translates (pathname,sha1) values into (inode, sha1) values. Bootloader should pass the SHA1 hash of the userspace helper application to kernel as command-line parameter. The value is used to authorize userspapce helper application. When the installer updates the reference hash list it is using OMAP Secure Environment services to create device specific HMAC signature. DigSig ====== The original code is based on DigSig open source software (available in http://disec.sourceforge.net/). DigSig relied solely on signed binaries and tried to sign all ELF executables by adding special signature section to executable files. DigSig project added RSA code from gnupg project to Linux kernel to do the verification. This feature is removed from Aegis Validator. It is using the stored list of reference hashes for verification. The original DigSig is meant to be used as Tripwire-like tool for system administrator to verify integrity of installed binaries. System administrators can RSA sign ELF applications and libraries and execution of non-signed ELF code can be prevented. Verification result is cached. Aegis Validator is using LSM hooks to catch executable code loading. The original DigSig was implemented as a loadable kernel module and verification public key is inserted using init script via sysfs kernel interface. Aegis Validator is mainly used to protect the system against offline tampering attempts and malware. Code structure and interfaces ============================= All code is in its own subtree (security/aegis/validator) except small LSM hook addon that was added for kernel module verification. Most of the code is generic. OMAP Security Environment specific code is in separate module (platsec.c). There are dependencies to kernel-based Aegis Runtime Policy Framework. Runtime Policy Framework source origin check is triggered by Aegis Validator. Validator is also using Runtime Policy Framework services for access checking. This functionality is separated in sidcheck.c module. Securityfs configuration interface is in /sys/kernel/security/validator. There are entries to enable and disable functionality and also to insert new reference hashes to the kernel. Cache and reference hash list content can also be dumped for debugging purpose. Validator securityfs entries are: hashlist - load new reference hash entries (write), print hashlist (read) cache - print verification cache (read) flush - flush verification cache (write) enabled - enable validator functionality (write), show value (read) enforce - set validator to enforcing mode (write), show value (read) modlist - authorize kernel modules (write), show value (read) Both enable and enforce allow integrity protection features to be enabled and enforced separately. The value is treated as a bit mask with fields (enforce control includes bits 0-3): bit 0: enable/enforce SHA1 hash calculation bit 1: enable/enforce source origin checking bit 2: enable/enforce data file checking bit 3: enable/enforce file attribute checking bit 4: invoke userspace helper to load more hashes bit 5: check only entries that are listed in the reference hash list bit 6: if set require the client to own "tcb" Runtime Policy Framework credential to allow adding new reference hashes. bit 7: if set then only allow loading of new hashes but no mode changes bit 8: if set then allow only whitelisted kernel modules The following LSM hooks are used to interface with the kernel: .netlink_send .file_mmap .file_free_security .dentry_open .inode_permission .inode_unlink .inode_create .inode_rename .bprm_check_security .inode_free_security .sb_free_security Reference hashlist values are loaded into kernel by invoking userspace helper application from kernel. The helper application is reading, verifying and parsing the reference hashlist file and creates the list of records to be sent into kernel via securityfs. The list consist of records having SHA1 hash, inode number, mount volume identifier and source origin identifier of the component. Software installer is also using securityfs interface to add new reference hashes into kernel. There is also netlink-based validation error notification mechanism to userspace. If Validator blocks loading then this error with some context information is also logged to netlink multicast socket (25). Features ======== SHA1 integrity check for executables Enable/Enforce: BIT(0) "HASH_CHECK_BIT" This is basic functionality of Aegis Validator. Applications and shared libraries are verified. The check is triggered by LSM hooks .file_mmap and .bprm_check_security. The object is read, SHA1 hash is calculated and positive verification result is cached. The object is only verified once in each boot session and later invocations only lookup verification result from cache. Source origin checking Enable/Enforce: BIT(1) SID_CHECK_BIT All executable objects are bound to some source origin. The origin is typically a repository which contains the Debian package having the executable object. Source origins can have different trust level, which means that applications from certain repository are allowed to grant more credentials like POSIX capabilities and resource tokens (concept of Aegis Runtime Policy Framework). When an object is loaded to execution Aegis Vaölidator is calling one Aegis Runtime Policy Framework kernel function to verify that the current process is allowed to load the object. Data file checking Enable/Enforce: BIT(2) DATA_CHECK_BIT There is also limited support to protect data files. Certain directories can be specified in the reference hash list to be protected directories and files in those directories should have matching reference hash. The hash value is checked in file open operation. If the hash does not match opening of the file is prevented. It is also possible to declare some files in the protected to be dynamic, which means that those files are not verified. Use cases for data file protection are: * Protection of upstart configuration files in /etc/init * Protection of kernel modules in /lib/modules/* * Protection of Perl libraries File attribute checking Enable/Enforce: BIT(3) ATTRIB_CHECK_BIT Aegis Validator also records file attributes (user, group, mode) and can verify that file attributes are still the same. Request to load more hashes Enable: BIT(4) HASH_REQ_BIT The hash list of the root filesystem is loaded during boot. If other volumes are mounted and something is executed from the mounted volume then userspace helper application is invoked to load new reference hashes to authorize execution of executables. Checking only listed files Enable: BIT(5) LISTED_ONLY_BIT It is also possible to limit reference hash checking to only those files that are listed in the reference hashlist. If source origin checking is configured then unlisted executables are treated to come from unknown source origin. Use "tcb" resource token to protect hash loading Enable: BIT(6) SECFS_BIT Userspace helper can configure Validator to require "tcb" resource token for clients loading new reference hashes. Sealing operation mode Enable: BIT(7) SEAL_BIT If the seal bit is set during configuration then further configuration changes (modifications to enable and enforce settings) are not allowed. It is still possible to load new reference hashes, but loading requires possession of tcb resource token. Kernel module whitelist mode Enable BIT(8) KMOD_BIT SHA1 hashes of authorized kernel modules can be loaded via modlist securityfs entry. If this bit is set then all kernel modules are verified when the module is loaded and only those that are in the whitelist are accepted. Hash input message format ========================= Securityfs entry /sys/kernel/security/validator/hashlist is used to load reference hashes to Aegis Validator. This is done either during startup or afterwards if the installer installs new software. There can be four different messages. The message can be: 1. Old format reference hash message (a) This message type is still supported because of backwards compatibility. When all userspace components and other tools have been updated this message can be deprecated. This message has been replaced by the new format message, which also includes file metadata fields and source identifier is numeric instead of string. 2. New format reference hash message (s|t) This message type adds file metadata fields and source identifier is also given as numeric identifier instead of textual string value. Exactly same format is used for executables (s) and static data (t). However those objects are still separately labeled in the hashlist. 3. Dynamic file entry (x) Integrity protected directories can also contain configuration files whose value can change. These files should be declared as dynamic. Those files are not integrity protected but writing can be controlled and file attributes can be verified. Note that this message does not include any hash value, because integrity of dynamic files is not verified. 4. Immutable directory entry (d) This message type specifies immutable directories. Also this message does not contain reference hash value. There are two additional fields that specify resource token and capability mask requirements for writing to the directory. If those values are zero writing is not controlled. 5. Protected directory entry (p) This message type specifies directories that should be protected against unauthorized renaming, removal and the use as a mount poinyt. Also this message does not contain reference hash value. There are two additional fields that specify resource token and capability mask requirements for writing to the directory. If those values are zero writing is not controlled. Field Description Length ----- ----------- ------ a = Code byte for old hash format (1) SHA1 = 20 bytes of SHA1 hash (20) blank = One blank character (1) devid = Device id as integer string (>=1) blank = One blank character (1) ino = inode number as integer string (>=1) blank = One blank character (1) sid = Source identifier string (>=1) null = '\0' character (1) newline = '\n' character (1) Field Description Length ----- ----------- ------ s|t = Code byte for new hash format (1) SHA1 = 20 bytes of SHA1 hash (20) blank = One blank character (1) devid = Device id as integer string (>=1) blank = One blank character (1) ino = inode number as integer string (>=1) blank = One blank character (1) uid = uid number as integer string (>=1) blank = One blank character (1) gid = gid number as integer string (>=1) blank = One blank character (1) mode = filemode number integer string (>=1) blank = One blank character (1) sid = Sid identifier as integer string (>=1) blank = One blank character (1) crednum = Number of credtype/credvalue pairs (>=1) Multiple credtype/credvalue pairs depending on crednum value If crednum was 0 then there won't be these fields blank = One blank character (1) id = Policy identifier as integer string (>=1) blank = One blank character (1) credtype = Credential type as integer string (>=1) blank = One blank character (1) credvalue = Credential value as integer string (>=1) null = '\0' character (1) newline = '\n' character (1) Field Description Length ----- ----------- ------ x = Code byte for dynamic files (1) devid = Device id as integer string (>=1) blank = One blank character (1) ino = inode number as integer string (>=1) blank = One blank character (1) uid = uid number as integer string (>=1) blank = One blank character (1) gid = gid number as integer string (>=1) blank = One blank character (1) mode = filemode number integer string (>=1) blank = One blank character (1) sid = Sid identifier as integer string (>=1) blank = One blank character (1) Multiple credtype/credvalue pairs depending on crednum value If crednum was 0 then there won't be these fields blank = One blank character (1) id = Policy identifier as integer string (>=1) blank = One blank character (1) credtype = Credential type as integer string (>=1) blank = One blank character (1) credvalue = Credential value as integer string (>=1) null = '\0' character (1) newline = '\n' character (1) Field Description Length ----- ----------- ------ d|p = Code byte for directory entry (1) devid = Device id as integer string (>=1) blank = One blank character (1) ino = inode number as integer string (>=1) blank = One blank character (1) uid = uid number as integer string (>=1) blank = One blank character (1) gid = gid number as integer string (>=1) blank = One blank character (1) mode = filemode number integer string (>=1) blank = One blank character (1) sid = Sid identifier as integer string (>=1) blank = One blank character (1) Multiple credtype/credvalue pairs depending on crednum value If crednum was 0 then there won't be these fields blank = One blank character (1) id = Policy identifier as integer string (>=1) blank = One blank character (1) credtype = Credential type as integer string (>=1) blank = One blank character (1) credvalue = Credential value as integer string (>=1) null = '\0' character (1) newline = '\n' character (1) So the minimum length of the message should be at least 14 bytes. There is odd message termination with "\0\n". The change requires changes to userspace tools as well. Potential future work ===================== Linux kernel version 2.6.30 introduced a new framework called Integrity Measurement Architecture (IMA). IMA framework partly overlaps with Aegis Validator as both are measuring loaded executables. However, IMA is just doing measurements and its purpose is to provide Trusted Platform Module (TPM) backed measurement agent inside Linux kernel, which can be used to generate signed replies (signed PCR registers + measurement log) to remote attestation requests. There is no local integrity enforcement in IMA, but there are plans to add such module called EVM. IMA code could be extended to provide an interface for integrity enforcement modules. This should be proposed. If succesful then Aegis Validator measurement code can be dropped in favor of IMA measurement code. As LSM hooks are not in general stackable it also makes sense to minimize the use of LSM hooks if there is a need to run another framework that relies on LSM.