diff --git a/crypto/fips140-eval-testing-uapi.h b/crypto/fips140-eval-testing-uapi.h new file mode 100644 index 000000000000..04e6cf633594 --- /dev/null +++ b/crypto/fips140-eval-testing-uapi.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _CRYPTO_FIPS140_EVAL_TESTING_H +#define _CRYPTO_FIPS140_EVAL_TESTING_H + +#include + +/* + * This header defines the ioctls that are available on the fips140 character + * device. These ioctls expose some of the module's services to userspace so + * that they can be tested by the FIPS certification lab; this is a required + * part of getting a FIPS 140 certification. These ioctls do not have any other + * purpose, and they do not need to be present in production builds. + */ + +/* + * Call the fips140_is_approved_service() function. The argument must be the + * service name as a NUL-terminated string. The return value will be 1 if + * fips140_is_approved_service() returned true, or 0 if it returned false. + */ +#define FIPS140_IOCTL_IS_APPROVED_SERVICE _IO('F', 0) + +/* + * Call the fips140_module_version() function. The argument must be a pointer + * to a buffer of size >= 256 chars. The NUL-terminated string returned by + * fips140_module_version() will be written to this buffer. + */ +#define FIPS140_IOCTL_MODULE_VERSION _IOR('F', 1, char[256]) + +#endif /* _CRYPTO_FIPS140_EVAL_TESTING_H */ diff --git a/crypto/fips140-eval-testing.c b/crypto/fips140-eval-testing.c index ef8edd0d1302..ea3cd653983a 100644 --- a/crypto/fips140-eval-testing.c +++ b/crypto/fips140-eval-testing.c @@ -7,9 +7,26 @@ * should not be included in production builds of the module. */ +/* + * We have to redefine inline to mean always_inline, so that _copy_to_user() + * gets inlined. This is needed for it to be placed into the correct section. + * See fips140_copy_to_user(). + * + * We also need to undefine BUILD_FIPS140_KO to allow the use of the code + * patching which copy_to_user() requires. + */ +#undef inline +#define inline inline __attribute__((__always_inline__)) __gnu_inline \ + __inline_maybe_unused notrace +#undef BUILD_FIPS140_KO + +#include +#include #include +#include #include "fips140-module.h" +#include "fips140-eval-testing-uapi.h" /* * This option allows deliberately failing the self-tests for a particular @@ -22,6 +39,9 @@ module_param_named(fail_selftest, fips140_fail_selftest, charp, 0); static bool fips140_fail_integrity_check; module_param_named(fail_integrity_check, fips140_fail_integrity_check, bool, 0); +static dev_t fips140_devnum; +static struct cdev fips140_cdev; + /* Inject a self-test failure (via corrupting the result) if requested. */ void fips140_inject_selftest_failure(const char *impl, u8 *result) { @@ -36,7 +56,74 @@ void fips140_inject_integrity_failure(u8 *textcopy) textcopy[0] ^= 0xff; } +static long fips140_ioctl_is_approved_service(unsigned long arg) +{ + const char *service_name = strndup_user((const char __user *)arg, 256); + long ret; + + if (IS_ERR(service_name)) + return PTR_ERR(service_name); + + ret = fips140_is_approved_service(service_name); + + kfree(service_name); + return ret; +} + +/* + * Code in fips140.ko is covered by an integrity check by default, and this + * check breaks if copy_to_user() is called. This is because copy_to_user() is + * an inline function that relies on code patching. However, since this is + * "evaluation testing" code which isn't included in the production builds of + * fips140.ko, it's acceptable to just exclude it from the integrity check. + */ +static noinline unsigned long __section("text.._fips140_unchecked") +fips140_copy_to_user(void __user *to, const void *from, unsigned long n) +{ + return copy_to_user(to, from, n); +} + +static long fips140_ioctl_module_version(unsigned long arg) +{ + const char *version = fips140_module_version(); + size_t len = strlen(version) + 1; + + if (len > 256) + return -EOVERFLOW; + + if (fips140_copy_to_user((void __user *)arg, version, len)) + return -EFAULT; + + return 0; +} + +static long fips140_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case FIPS140_IOCTL_IS_APPROVED_SERVICE: + return fips140_ioctl_is_approved_service(arg); + case FIPS140_IOCTL_MODULE_VERSION: + return fips140_ioctl_module_version(arg); + default: + return -ENOTTY; + } +} + +static const struct file_operations fips140_fops = { + .unlocked_ioctl = fips140_ioctl, +}; + bool fips140_eval_testing_init(void) { + if (alloc_chrdev_region(&fips140_devnum, 1, 1, "fips140") != 0) { + pr_err("failed to allocate device number\n"); + return false; + } + cdev_init(&fips140_cdev, &fips140_fops); + if (cdev_add(&fips140_cdev, fips140_devnum, 1) != 0) { + pr_err("failed to add fips140 character device\n"); + return false; + } return true; } diff --git a/samples/crypto/fips140_lab_test.c b/samples/crypto/fips140_lab_test.c new file mode 100644 index 000000000000..dd2324b8f84a --- /dev/null +++ b/samples/crypto/fips140_lab_test.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2021 Google LLC + * + * This is a sample program which calls some ioctls on /dev/fips140 and prints + * the results. The purpose of this program is to allow the FIPS certification + * lab to test some services of fips140.ko, which they are required to do. This + * is a sample program only, and it can be modified by the lab as needed. This + * program must be run as root, and it only works if the system has loaded a + * build of fips140.ko with evaluation testing support enabled. + * + * This program can be compiled and run on an Android device as follows: + * + * NDK_DIR=$HOME/android-ndk-r23b # adjust directory path as needed + * $NDK_DIR/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang \ + * fips140_lab_test.c -O2 -Wall -o fips140_lab_test + * adb push fips140_lab_test /data/local/tmp/ + * adb root + * adb shell /data/local/tmp/fips140_lab_test + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../crypto/fips140-eval-testing-uapi.h" + +static int fips140_dev_fd = -1; + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +static const char *booltostr(bool b) +{ + return b ? "true" : "false"; +} + +static void __attribute__((noreturn)) +do_die(const char *format, va_list va, int err) +{ + fputs("ERROR: ", stderr); + vfprintf(stderr, format, va); + if (err) + fprintf(stderr, ": %s", strerror(err)); + putc('\n', stderr); + exit(1); +} + +static void __attribute__((noreturn, format(printf, 1, 2))) +die_errno(const char *format, ...) +{ + va_list va; + + va_start(va, format); + do_die(format, va, errno); + va_end(va); +} + +static void __attribute__((noreturn, format(printf, 1, 2))) +die(const char *format, ...) +{ + va_list va; + + va_start(va, format); + do_die(format, va, 0); + va_end(va); +} + +static int get_fips140_device_number(void) +{ + FILE *f; + char line[128]; + int number; + char name[32]; + + f = fopen("/proc/devices", "r"); + if (!f) + die_errno("failed to open /proc/devices"); + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "%d %31s", &number, name) == 2 && + strcmp(name, "fips140") == 0) + return number; + } + fclose(f); + die("fips140 device node is unavailable.\n" +"The fips140 device node is only available when the fips140 module is loaded\n" +"and has been built with evaluation testing support."); +} + +static void create_fips140_node_if_needed(void) +{ + struct stat stbuf; + int major; + + if (stat("/dev/fips140", &stbuf) == 0) + return; + + major = get_fips140_device_number(); + if (mknod("/dev/fips140", S_IFCHR | 0600, makedev(major, 1)) != 0) + die_errno("failed to create fips140 device node"); +} + +static bool fips140_is_approved_service(const char *name) +{ + int ret = ioctl(fips140_dev_fd, FIPS140_IOCTL_IS_APPROVED_SERVICE, name); + + if (ret < 0) + die_errno("FIPS140_IOCTL_IS_APPROVED_SERVICE unexpectedly failed"); + if (ret == 1) + return true; + if (ret == 0) + return false; + die("FIPS140_IOCTL_IS_APPROVED_SERVICE returned unexpected value %d", + ret); +} + +static const char *fips140_module_version(void) +{ + char buf[256]; + char *str; + int ret = ioctl(fips140_dev_fd, FIPS140_IOCTL_MODULE_VERSION, buf); + + if (ret < 0) + die_errno("FIPS140_IOCTL_MODULE_VERSION unexpectedly failed"); + if (ret != 0) + die("FIPS140_IOCTL_MODULE_VERSION returned unexpected value %d", ret); + str = strdup(buf); + if (!str) + die("out of memory"); + return str; +} + +static const char * const services_to_check[] = { + "aes", + "cbc(aes)", + "cbcmac(aes)", + "cmac(aes)", + "ctr(aes)", + "cts(cbc(aes))", + "ecb(aes)", + "essiv(cbc(aes),sha256)", + "gcm(aes)", + "hmac(sha1)", + "hmac(sha224)", + "hmac(sha256)", + "hmac(sha384)", + "hmac(sha512)", + "jitterentropy_rng", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "stdrng", + "xcbc(aes)", + "xts(aes)", +}; + +int main(void) +{ + size_t i; + + if (getuid() != 0) + die("This program requires root. Run 'adb root' first."); + + create_fips140_node_if_needed(); + + fips140_dev_fd = open("/dev/fips140", O_RDONLY); + if (fips140_dev_fd < 0) + die_errno("failed to open /dev/fips140"); + + printf("fips140_module_version() => \"%s\"\n", fips140_module_version()); + for (i = 0; i < ARRAY_SIZE(services_to_check); i++) { + const char *service = services_to_check[i]; + + printf("fips140_is_approved_service(\"%s\") => %s\n", service, + booltostr(fips140_is_approved_service(service))); + } + return 0; +} diff --git a/scripts/module.lds.S b/scripts/module.lds.S index 8b9dcec11d1e..6cca5d88744e 100644 --- a/scripts/module.lds.S +++ b/scripts/module.lds.S @@ -71,6 +71,7 @@ SECTIONS { *(.text..L.cfi.jumptable .text..L.cfi.jumptable.*) __cfi_jt_end = .; *(.text.._end) + *(.text.._fips140_unchecked) } #endif }