ANDROID: fips140: add userspace interface for evaluation testing

The FIPS lab is required to test the service indicators and version
information services of the module, i.e. the
fips140_is_approved_service() and fips140_module_version() functions.
There are several ways we could support this:

- Implement the tests in the module ourselves.  However it's unclear
  that CMVP would allow this, and we would need the full list of tests,
  which could change over time depending on what the lab decides to do.

- Support the lab writing, building, and loading a custom kernel module
  (or a custom kernel image) that tests these functions.

- Provide a userspace interface to these services, restricted to builds
  with CONFIG_CRYPTO_FIPS140_MOD_EVAL_TESTING=y.  This would allow
  writing the tests in userspace, which would be much easier.

Implement the last solution, since it's the easier of the two solutions
that are "guaranteed" to be allowed.  Make the module register a char
device which supports some ioctls, one per function that needs to be
tested.  Also provide some sample userspace code in samples/crypto/.

Note: copy_to_user() would break the integrity check, so take some care
to exclude it.  This is allowed since this is non-production code.

Bug: 188620248
Change-Id: Ic256d9c5bd4d0c57ede88a3e3e76e89554909b38
Signed-off-by: Eric Biggers <ebiggers@google.com>
This commit is contained in:
Eric Biggers 2021-11-18 16:09:11 -08:00
parent 97fb2104fe
commit 109f31ac23
4 changed files with 305 additions and 0 deletions

View File

@ -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 <linux/ioctl.h>
/*
* 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 */

View File

@ -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 <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/slab.h>
#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;
}

View File

@ -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 <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#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;
}

View File

@ -71,6 +71,7 @@ SECTIONS {
*(.text..L.cfi.jumptable .text..L.cfi.jumptable.*)
__cfi_jt_end = .;
*(.text.._end)
*(.text.._fips140_unchecked)
}
#endif
}