Linux与BL31之间添加SMC实现随机数获取

需求

假设一款Armv8-A架构的芯片,有一个硬件真随机数生成器TRNG,是一个安全的Master,Linux用户应用层想获取硬件真随机数。

方案

由于TRNG是一个安全的master,CPU只有处于安全状态才能访问,而Kernel是处于非安全状态,无法直接访问该硬件真随机数生成器,因此需要切换到安全状态。

  1. BL31位于EL3,处于安全状态,实现真随机数的驱动;
  2. Kernel中添加真随机数设备hwrng驱动,供用户侧调用;
  3. BL31与Kernel之间通过SMC进行安全与非安全状态切换;
  4. 用户侧使用/dev/hwrng设备进行真随机数的读取操作;

本方案基于ARM的开源固件ATF,需要对其有一定的了解,详细可参考本公众号/博客的关于ATF的文章。

环境搭建

使用QEMU模拟一套Linux环境,使用ATF启动Linux kernel。

工具链

安装gcc工具链、gdb调试器和qemu模拟器。

sudo apt-get install gcc-aarch64-linux-gnu gdb-multiarch qemu-system-arm

准备镜像

启动需要如下镜像,主要用到atf,edk2,rootfs,kernel这些镜像。

阶段 文件
BL1 build/qemu/debug/bl1.bin
BL2 build/qemu/debug/bl2.bin
BL31 build/qemu/debug/bl31.bin
BL33 QEMU_EFI.fd
Kernel arch/arm64/boot/Image
Rootfs output/images/rootfs.cpio.gz

准备一个目录qemu_boot存放所有镜像文件,最终启动需要的镜像如下所示。

Image  QEMU_EFI.fd  bl1.bin  bl2.bin  bl31.bin fip.bin  flash.bin  rootfs.cpio.gz

下面介绍如何编译生成这些启动镜像。

EDK2

下载QEMU_EFI。

wget http://snapshots.linaro.org/components/kernel/leg-virt-tianocore-edk2-upstream/latest/QEMU-KERNEL-AARCH64/RELEASE_GCC5/QEMU_EFI.fd

rootfs

rootfs使用Buildroot编译构建。

git clone git://git.buildroot.net/buildroot.git
cd buildroot
make qemu_aarch64_virt_defconfig
utils/config -e BR2_TARGET_ROOTFS_CPIO
utils/config -e BR2_TARGET_ROOTFS_CPIO_GZIP
make olddefconfig
make

编译得到output/images/rootfs.cpio.gz

kernel

kernel构建这里下载linux-5.17.2.tar.xz。

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.17.2.tar.xz
cd linux-5.17.2

编译kernel,如下。

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j16

编译得到arch/arm64/boot/Image

ATF

下载ATF源码,这里下载lts-v2.8.0长期支持版。

wget https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git/snapshot/trusted-firmware-a-lts-v2.8.0.tar.gz
tar -xvf trusted-firmware-a-lts-v2.8.0.tar.gz

编译ATF,如下。

make CROSS_COMPILE=aarch64-linux-gnu- PLAT=qemu all fip DEBUG=1 \
BL33=../qemu_boot/QEMU_EFI.fd

build/qemu/debug会生成bl1.binbl2.binbl31.bin文件以及固件镜像包fip.bin

打包

将上述编译得到的所有文件放入qemu_boot目录中,并将bl1.binfip.bin存放在一个flash.bin文件中,通过提供给qemu的-bios参数加载。

dd if=bl1.bin of=flash.bin bs=4096 conv=notrunc
dd if=fip.bin of=flash.bin seek=64 bs=4096 conv=notrunc

QEMU启动

使用qemu模拟启动,进入qemu_boot目录,命令如下。

qemu-system-aarch64 -nographic -machine virt,secure=on \
    -cpu cortex-a57 -kernel Image \
    -append 'console=ttyAMA0,38400 keep_bootcon' \
    -initrd rootfs.cpio.gz -smp 2 -m 1024 -bios flash.bin \
    -d unimp

不出意外,kernel系统启动正常,打印如下,账户输入root即可。

[    0.767766] Run /init as init process
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Saving 2048 bits of non-creditable seed for next boot
Starting network: udhcpc: started, v1.36.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
deleting routers
adding dns 10.0.2.3
OK

Welcome to Buildroot
buildroot login: root
# 
# 

BL31开发

BL31在启动完成后会常驻内存,处理来自低异常等级的SMC异常,这些异常处理流程就是运行时服务,不同的服务在调用之前需要先注册到BL31中。在ATF提供的源码中有一些默认的服务,位于services文件夹如下。

services/
├── arm_arch_svc    ---架构相关的服务
│   └── arm_arch_svc_setup.c
├── spd    ---Trusted-OS相关的服务
│   ├── opteed
│   ├── pncd
│   ├── tlkd
│   ├── trusty
│   └── tspd
└── std_svc    ---标准服务
    ├── drtm
    ├── pci_svc.c
    ├── rmmd
    ├── sdei
    ├── spm
    ├── spmd
    ├── std_svc_setup.c
    └── trng

可以看到标准服务中提供trng的服务,这里我们打算使用这个服务,不再自己定义服务了。由于使用的是qemu模拟环境,没有硬件真随机数,因此在这个trng中的也模拟一个真随机数生成器。

SMC

在ARM架构中,SMC异常用于非安全状态切换到安全状态。SMC(Secure Monitor Call)指令用于生成一个同步异常,由EL3运行的Secure Monitor安全监控代码处理。SMC参数通过寄存器进行传递,这些调用可能会进入到S-EL1下的Trusted OS 。

SMC指令分为两种:

  • SMC32:32位或者64位的客户端代码使用,可以最多传递6个32位参数
  • SMC64:只能由64位的客户端代码使用,可以最多传递6个64位参数

SMC函数ID是32位的整型值,每一位的用法如下。
Linux与BL31之间添加SMC实现随机数获取_第1张图片

SMC Handler

BL31中标准服务SMC的顶级入口处理函数如下,对不同类型的服务进行了判断,并进入相应的服务处理程序。

/*
 * Top-level Standard Service SMC handler. This handler will in turn dispatch
 * calls to PSCI SMC handler
 */
static uintptr_t std_svc_smc_handler(uint32_t smc_fid,
			     u_register_t x1,
			     u_register_t x2,
			     u_register_t x3,
			     u_register_t x4,
			     void *cookie,
			     void *handle,
			     u_register_t flags)
{
	if (((smc_fid >> FUNCID_CC_SHIFT) & FUNCID_CC_MASK) == SMC_32) {
		/* 32-bit SMC function, clear top parameter bits */

		x1 &= UINT32_MAX;
		x2 &= UINT32_MAX;
		x3 &= UINT32_MAX;
		x4 &= UINT32_MAX;
	}
    ......
        
#if TRNG_SUPPORT
	if (is_trng_fid(smc_fid)) {
		return trng_smc_handler(smc_fid, x1, x2, x3, x4, cookie, handle,
				flags);
	}
#endif /* TRNG_SUPPORT */
	
    ......

	switch (smc_fid) {
	case ARM_STD_SVC_CALL_COUNT:
		/*
		 * Return the number of Standard Service Calls. PSCI is the only
		 * standard service implemented; so return number of PSCI calls
		 */
		SMC_RET1(handle, PSCI_NUM_CALLS);

	case ARM_STD_SVC_UID:
		/* Return UID to the caller */
		SMC_UUID_RET(handle, arm_svc_uid);

	case ARM_STD_SVC_VERSION:
		/* Return the version of current implementation */
		SMC_RET2(handle, STD_SVC_VERSION_MAJOR, STD_SVC_VERSION_MINOR);

	default:
		VERBOSE("Unimplemented Standard Service Call: 0x%x \n", smc_fid);
		SMC_RET1(handle, SMC_UNK);
	}
}

/* Register Standard Service Calls as runtime service */
DECLARE_RT_SVC(
		std_svc,

		OEN_STD_START,
		OEN_STD_END,
		SMC_TYPE_FAST,
		std_svc_setup,
		std_svc_smc_handler
);

其中如果支持TRNG,就判断调用过来的smc_fid是否属于trng服务,并进入trng_smc_handler处理,如下。

uintptr_t trng_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
			   u_register_t x3, u_register_t x4, void *cookie,
			   void *handle, u_register_t flags)
{
	if (!memcmp(&plat_trng_uuid, &uuid_null, sizeof(uuid_t))) {
		SMC_RET1(handle, TRNG_E_NOT_IMPLEMENTED);
	}

	switch (smc_fid) {
	case ARM_TRNG_VERSION:
		SMC_RET1(handle, MAKE_SMCCC_VERSION(
			TRNG_VERSION_MAJOR, TRNG_VERSION_MINOR));
		break; /* unreachable */

	case ARM_TRNG_FEATURES:
		if (is_trng_fid((uint32_t)x1)) {
			SMC_RET1(handle, TRNG_E_SUCCESS);
		} else {
			SMC_RET1(handle, TRNG_E_NOT_SUPPORTED);
		}
		break; /* unreachable */

	case ARM_TRNG_GET_UUID:
		SMC_UUID_RET(handle, plat_trng_uuid);
		break; /* unreachable */

	case ARM_TRNG_RND32:
		return trng_rnd32((uint32_t)x1, handle);

	case ARM_TRNG_RND64:
		return trng_rnd64((uint32_t)x1, handle);

	default:
		WARN("Unimplemented TRNG Service Call: 0x%x\n", smc_fid);
		SMC_RET1(handle, TRNG_E_NOT_IMPLEMENTED);
		break; /* unreachable */
	}
}

可以看到TRNG有不同请求的SMC ID,我们打算使用ARM_TRNG_RND64来获取随机数,接口函数为trng_rnd64,定义如下。

/* handle the RND call in SMC 64 bit mode */
static uintptr_t trng_rnd64(uint32_t nbits, void *handle)
{
	uint64_t mask = ~0ULL;
	uint64_t ent[3] = {0};

	if (nbits == 0U || nbits > TRNG_RND64_ENTROPY_MAXBITS) {
		SMC_RET1(handle, TRNG_E_INVALID_PARAMS);
	}

	if (!trng_pack_entropy(nbits, &ent[0])) {
		SMC_RET1(handle, TRNG_E_NO_ENTROPY);
	}

	/* Mask off higher bits if only part of register requested */
	if ((nbits % 64U) != 0U) {
		mask >>= 64U - (nbits % 64U);
	}

	switch ((nbits - 1U) / 64U) {
	case 0:
		SMC_RET4(handle, TRNG_E_SUCCESS, 0, 0, ent[0] & mask);
		break; /* unreachable */
	case 1:
		SMC_RET4(handle, TRNG_E_SUCCESS, 0, ent[1] & mask, ent[0]);
		break; /* unreachable */
	case 2:
		SMC_RET4(handle, TRNG_E_SUCCESS, ent[2] & mask, ent[1], ent[0]);
		break; /* unreachable */
	default:
		SMC_RET1(handle, TRNG_E_INVALID_PARAMS);
		break; /* unreachable */
	}
}

每次调用最多获取192bit的随机数,从trng熵池中获取,这个熵池的熵源又会使用硬件真随机数进行填充。TRNG ID和错误码等定义在trng_svc.h中。

/* SMC function IDs for TRNG queries */
#define ARM_TRNG_VERSION	U(0x84000050)
#define ARM_TRNG_FEATURES	U(0x84000051)
#define ARM_TRNG_GET_UUID	U(0x84000052)
#define ARM_TRNG_RND32		U(0x84000053)
#define ARM_TRNG_RND64		U(0xC4000053)

/* TRNG version numbers */
#define TRNG_VERSION_MAJOR	(0x1)
#define TRNG_VERSION_MINOR	(0x0)

/* TRNG Error Numbers */
#define TRNG_E_SUCCESS		(0)
#define TRNG_E_NOT_SUPPORTED	(-1)
#define TRNG_E_INVALID_PARAMS	(-2)
#define TRNG_E_NO_ENTROPY	(-3)
#define TRNG_E_NOT_IMPLEMENTED	(-4)

/* TRNG Entropy Bit Numbers */
#define TRNG_RND32_ENTROPY_MAXBITS	(96U)
#define TRNG_RND64_ENTROPY_MAXBITS	(192U)

TRNG移植

根据ATF官方提供的关于TRNG的TRNG porting requirements,TRNG移植需要提供如下值和函数定义。

  • uuid_t plat_trng_uuid:TRNG的UUID
  • void plat_entropy_setup:TRNG硬件相关的初始化
  • plat_get_entropy:获取熵源

这里我们移植TRNG参考了juno平台,源码位于juno_trng.c

移植

plat/qemu/common目录下添加qemu_trng.c源文件,源码如下。

#include 
#include 
#include 
#include 

#include 
#include 
#include 

DEFINE_SVC_UUID2(_plat_trng_uuid,
	0x23523c58, 0x7448, 0x4083, 0x9d, 0x16,
	0xe3, 0xfa, 0xb9, 0xf1, 0x73, 0xbc
);
uuid_t plat_trng_uuid;

/*
 * Uses the dummy peripheral on qemu to return 8 bytes of
 * entropy. Returns 'true' when done successfully, 'false' otherwise.
 */
bool plat_get_entropy(uint64_t *out)
{
	assert(out);
	assert(!check_uptr_overflow((uintptr_t)out, sizeof(*out)));

	/* 真实平台需要使用硬件TRNG,这里测试用定时器计数值模拟 */
	*out = (read_cntpct_el0() << 32) + read_cntpct_el0();
	INFO("Get entropy on qemu platform, entropy=%ld\n", *out);

	return true;
}

void plat_entropy_setup(void)
{
	uint64_t dummy;

	plat_trng_uuid = _plat_trng_uuid;

	/* Initialise the entropy source and trigger RNG generation */
	plat_get_entropy(&dummy);
}

由于qemu平台没有硬件真随机数外设,我们使用定时器计数值进行模拟。

另外,需要将上面的qemu_trng.c添加进编译,在plat\qemu\qemu\platform.mk中添加TRNG编译。

ifeq (${ARM_ARCH_MAJOR},8)
BL31_SOURCES		+=	lib/cpus/aarch64/aem_generic.S		\
				lib/cpus/aarch64/cortex_a53.S		\
				lib/cpus/aarch64/cortex_a57.S		\
				lib/cpus/aarch64/cortex_a72.S		\
				lib/cpus/aarch64/qemu_max.S		\
				lib/semihosting/semihosting.c		\
				lib/semihosting/${ARCH}/semihosting_call.S \
				plat/common/plat_psci_common.c		\
				drivers/arm/pl061/pl061_gpio.c		\
				drivers/gpio/gpio.c			\
				${PLAT_QEMU_COMMON_PATH}/qemu_pm.c			\
				${PLAT_QEMU_COMMON_PATH}/topology.c			\
				${PLAT_QEMU_COMMON_PATH}/aarch64/plat_helpers.S	\
				${PLAT_QEMU_COMMON_PATH}/qemu_bl31_setup.c		\
				${QEMU_GIC_SOURCES}

ifeq (${SPD},spmd)
BL31_SOURCES		+=	plat/qemu/common/qemu_spmd_manifest.c
endif
# 添加QEMU TRNG
ifeq (${TRNG_SUPPORT},1)
BL31_SOURCES		+=	plat/qemu/common/qemu_trng.c
endif
endif

编译

由于需要支持TRNG,我们在编译时需要开启TRNG_SUPPORT宏,如下。

make CROSS_COMPILE=aarch64-linux-gnu- PLAT=qemu all fip DEBUG=1 TRNG_SUPPORT=1 \
BL33=../qemu_boot/QEMU_EFI.fd

编译成功后,将build/qemu/debug目录下的bl1.binbl2.binbl31.bin文件以及固件镜像包fip.bin拷贝到qemu_boot目录。

Kernel开发

hwrng

hw_random框架利用了硬件真随机数生成器,包括两个部分:内核提供/dev/hwrng字符设备及其对sysfs的支持;hw_random硬件驱动,并注册到内核。

rngd/rngtest
/dev/hwrng
hwrng-core
hwrng-driver

hwrng提供的驱动结构体及对外的接口如下,位于linux-5.17.2/include/linux/hw_random.h中,如下。

/**
 * struct hwrng - Hardware Random Number Generator driver
 * @name:		Unique RNG name.
 * @init:		Initialization callback (can be NULL).
 * @cleanup:		Cleanup callback (can be NULL).
 * @data_present:	Callback to determine if data is available
 *			on the RNG. If NULL, it is assumed that
 *			there is always data available.  *OBSOLETE*
 * @data_read:		Read data from the RNG device.
 *			Returns the number of lower random bytes in "data".
 *			Must not be NULL.    *OBSOLETE*
 * @read:		New API. drivers can fill up to max bytes of data
 *			into the buffer. The buffer is aligned for any type
 *			and max is a multiple of 4 and >= 32 bytes.
 * @priv:		Private data, for use by the RNG driver.
 * @quality:		Estimation of true entropy in RNG's bitstream
 *			(in bits of entropy per 1024 bits of input;
 *			valid values: 1 to 1024, or 0 for unknown).
 */
struct hwrng {
	const char *name;
	int (*init)(struct hwrng *rng);
	void (*cleanup)(struct hwrng *rng);
	int (*data_present)(struct hwrng *rng, int wait);
	int (*data_read)(struct hwrng *rng, u32 *data);
	int (*read)(struct hwrng *rng, void *data, size_t max, bool wait);
	unsigned long priv;
	unsigned short quality;

	/* internal. */
	struct list_head list;
	struct kref ref;
	struct completion cleanup_done;
};

struct device;

/** Register a new Hardware Random Number Generator driver. */
extern int hwrng_register(struct hwrng *rng);
extern int devm_hwrng_register(struct device *dev, struct hwrng *rng);
/** Unregister a Hardware Random Number Generator driver. */
extern void hwrng_unregister(struct hwrng *rng);
extern void devm_hwrng_unregister(struct device *dve, struct hwrng *rng);
/** Feed random bits into the pool. */
extern void add_hwgenerator_randomness(const char *buffer, size_t count, size_t entropy);

其中struct hwrng结构体是hw-randwom的硬件驱动,包括RNG名、初始化、读取、熵质量等函数,然后通过hwrng_register()或者devm_hwrng_register()注册到hwrng核心中,这样应用层就可以操作/dev/hwrng设备。

添加RNG驱动

驱动

不同厂商的RNG驱动源码位于linux-5.17.2/drivers/char/hw_random中,我们这里也在该目录添加qemu-rng.c源文件,实现通过SMC调用获取BL31的真随机数。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* SMC function IDs for TRNG queries */
#define ARM_TRNG_VERSION	UL(0x84000050)
#define ARM_TRNG_FEATURES	UL(0x84000051)
#define ARM_TRNG_GET_UUID	UL(0x84000052)
#define ARM_TRNG_RND32		UL(0x84000053)
#define ARM_TRNG_RND64		UL(0xc4000053)

/* TRNG Entropy Bit Numbers */
#define TRNG_RND32_ENTROPY_MAXBITS	(96U)
#define TRNG_RND64_ENTROPY_MAXBITS	(192U)

#ifndef PUT_UINT64_BE
#define PUT_UINT64_BE( n, data, offset )                        		\
{                                                               		\
    ( data )[( offset )    ] = (unsigned char) ( (n) >> 56 );         	\
    ( data )[( offset ) + 1] = (unsigned char) ( (n) >> 48 );         	\
    ( data )[( offset ) + 2] = (unsigned char) ( (n) >> 40 );         	\
    ( data )[( offset ) + 3] = (unsigned char) ( (n) >> 32 );         	\
    ( data )[( offset ) + 4] = (unsigned char) ( (n) >> 24 );         	\
    ( data )[( offset ) + 5] = (unsigned char) ( (n) >> 16 );         	\
    ( data )[( offset ) + 6] = (unsigned char) ( (n) >> 8  );         	\
    ( data )[( offset ) + 7] = (unsigned char) ( (n)       );         	\
}
#endif

static int qemu_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
{
	size_t read = 0;
	struct arm_smccc_res res;

	pr_debug("rng read %ld\n", max);

	while (read < max) {
		arm_smccc_smc(ARM_TRNG_RND64, TRNG_RND64_ENTROPY_MAXBITS, 0, 0, 0, 0, 0, 0, &res);
		if (res.a0) {
			pr_err("TRNG SMC call failed %ld\n", res.a0);
			return -EIO;		
		}
		PUT_UINT64_BE(res.a1, (unsigned char*)buf, read + 0);
		PUT_UINT64_BE(res.a2, (unsigned char*)buf, read + 8);
		PUT_UINT64_BE(res.a3, (unsigned char*)buf, read + 16);
		read += TRNG_RND64_ENTROPY_MAXBITS / 8;
	}

	return read;
}

static struct hwrng qemu_rng = {
	.name		= "qemu-rng",
	.read		= qemu_rng_read,
};

static int __init rng_init(void)
{
	int err;

	pr_info("QEMU RNG detected\n");
	err = hwrng_register(&qemu_rng);
	if (err) {
		pr_err("RNG registering failed (%d)\n", err);
		goto out;
	}
out:
	return err;
}

static void __exit rng_exit(void)
{
	hwrng_unregister(&qemu_rng);
}

module_init(rng_init);
module_exit(rng_exit);

MODULE_LICENSE("GPL V2");
MODULE_AUTHOR("xxx ");
MODULE_DESCRIPTION("QEMU random number generator driver");

上述代码注册了一个硬件随机数生成器驱动qemu_rng,主要实现了随机数的读取操作qemu_rng_read,函数中循环读取需要的随机数长度。Kernel中调用SMC的函数为arm_smccc_smc,定义如下。

struct arm_smccc_res {
	unsigned long a0;
	unsigned long a1;
	unsigned long a2;
	unsigned long a3;
};

/**
 * __arm_smccc_smc() - make SMC calls
 * @a0-a7: arguments passed in registers 0 to 7
 * @res: result values from registers 0 to 3
 * @quirk: points to an arm_smccc_quirk, or NULL when no quirks are required.
 *
 * This function is used to make SMC calls following SMC Calling Convention.
 * The content of the supplied param are copied to registers 0 to 7 prior
 * to the SMC instruction. The return values are updated with the content
 * from register 0 to 3 on return from the SMC instruction.  An optional
 * quirk structure provides vendor specific behavior.
 */
#ifdef CONFIG_HAVE_ARM_SMCCC
asmlinkage void __arm_smccc_smc(unsigned long a0, unsigned long a1,
			unsigned long a2, unsigned long a3, unsigned long a4,
			unsigned long a5, unsigned long a6, unsigned long a7,
			struct arm_smccc_res *res, struct arm_smccc_quirk *quirk);
#else
static inline void __arm_smccc_smc(unsigned long a0, unsigned long a1,
			unsigned long a2, unsigned long a3, unsigned long a4,
			unsigned long a5, unsigned long a6, unsigned long a7,
			struct arm_smccc_res *res, struct arm_smccc_quirk *quirk)
{
	*res = (struct arm_smccc_res){};
}
#endif

#define arm_smccc_smc(...) __arm_smccc_smc(__VA_ARGS__, NULL)

函数入参传递通过寄存器a0-a7,a0为SMC ID,返回结果来自struct arm_smccc_res *res,即寄存器a0-a3,a0为调用返回状态(错误码)。如果使用SMC64,传递参数寄存器大小为64bit,如果使用SMC32,大小为32bit。

这里我们第一个入参为获取随机数的的SMC ID,即ARM_TRNG_RND64,第二个参数为获取的最大熵源TRNG_RND64_ENTROPY_MAXBITS,其它入参不需要,填写为0。SMC返回的结果a1-a3为获取到的随机数,每次最多192位即24字节,并将随机数转换到输出缓存中。

最后需要将qemu-rng.c源码添加进编译,在同级目录下的Makefile中添加如下代码。

obj-$(CONFIG_HW_RANDOM_QEMU) += qemu-rng.o

配置

修改hw_randwom目录下的Kconfig,添加对于QEMU的random驱动的配置,如下。

config HW_RANDOM_QEMU
	tristate "QEMU HW Random Number Generator support"
	depends on HW_RANDOM
	help
	  This driver provides kernel-side support for the Random Number
	  Generator hardware found on QEMU series of SoCs.

	  Say 'Y' to enable the True Random Number Generator driver for QEMU
	  Choose 'M' to compile this driver as a module. The module
	  will be called qemu-rng.
	  If unsure, say 'N'.

这样就可以在menuconfig配置中选择开启QEMU硬件真随机数驱动。

编译

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

进入kernel图形化配置界面,默认硬件随机数是编译成模块的,我们这里为了方便,直接打进内核中,将QEMU HW Random Number Generator使能,即输入Y,然后退出保存即可。

Device Drivers --> Character cevices --> Hardware Random Number Generator Core support --> QEMU HW Random Number Generator support

再次编译,并将arch/arm64/boot/Image拷贝到qemu_boot目录。

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j16

随机数应用开发

在用户层编写一个随机数测试的应用,使用open打开硬件随机数设备,然后再read读取随机数,最后打印输出。

#include 
#include 
#include 
#include 
#include  

#define TRNG_DEVICE                       "/dev/hwrng"

static void hexdump(const char *name, const unsigned char *buffer, unsigned int len)
{
    printf("****************%s****************\n", name);
    for (unsigned int i = 0; i < len; i++) {
        printf("%02x ", buffer[i]);
        if ((i + 1) % 16 == 0) {
            printf("\n");
        }
    }
    if (len % 16 ) {
        printf("\n");
    }
}

int main(void)
{
    int ret;
    int fd;
    uint8_t buf[32];

    /* 打开设备 */
    fd = open(TRNG_DEVICE, O_RDONLY);
    if (fd < 0) {
        printf("Failed to open hwrng device\n");
        return -1;
    }
    /* 读取随机数 */
    ret = read(fd, buf, sizeof(buf));
    if (ret < 0) {
        printf("Failed to read random, ret=%d\n", ret);
        goto exit;
    }

    hexdump("random", buf, sizeof(buf));
exit:
    close(fd);

    return ret;
}

编译

aarch64-linux-gnu-gcc trng.c -o trng

编译生成trng可执行应用,并拷贝到qemu_boot文件夹中。

测试验证

打包及启动

首先同理按照环境搭建章节进行镜像打包,进入qemu_boot目录。

dd if=bl1.bin of=flash.bin bs=4096 conv=notrunc
dd if=fip.bin of=flash.bin seek=64 bs=4096 conv=notrunc

由于trng可执行应用没有打进rootfs中,这里我们使用共享文件夹的方法在QEMU虚拟机和宿主机之间传输文件。使用qemu进行启动,并使用-virtfs选项参数,指定共享文件夹的目录,这里即当前目录qemu_boot

qemu-system-aarch64 -nographic -machine virt,secure=on \
    -cpu cortex-a57 -kernel Image \
    -append 'console=ttyAMA0,38400 keep_bootcon' \
    -initrd rootfs.cpio.gz -smp 2 -m 1024 -bios flash.bin \
    -d unimp \
    -virtfs local,path=.,mount_tag=host0,security_model=passthrough,id=host0

挂载

启动进入Linux后,需要挂载共享文件夹qemu_boot

mkdir trng 
mount -t 9p -o trans=virtio,version=9p2000.L host0 /root/trng

这样qemu虚拟就可以访问宿主机的共享目录。

验证

进入/root/trng目录,执行trng应用。

./trng
# 如果打印类似如下说明切换到BL31获取随机数成功
INFO:    Get entropy on qemu platform, entropy=0x6a3c64f46a3c64fd
****************random****************
73 5a 0b b1 73 5a 0b b7 73 59 e4 cc 73 59 e4 d2 
73 59 b6 fc 73 59 b7 02 45 31 2e e4 45 31 2e e3

其实也可以不编写trng应用,直接读取/dev/hwrng设备,也可以获取到随机数,如下。

hexdump -C /dev/hwrng -n 32
# 如果打印类似如下说明切换到BL31获取随机数成功
INFO:    Get entropy on qemu platform, entropy=0x55ae335455ae3355
00000000  55 2b 0a 90 55 2b 0a 8e  55 2a f1 a6 55 2a f1 a2  |U+..U+..U*..U*..|

总结

Armv8架构中有四个异常等级EL0-EL3,两种安全状态即安全和非安全状态。SVC指令用于EL0下的用户应用程序请求EL1下的操作系统的服务,SMC指令允许普通世界中的程序请求EL3下的安全监控器的服务,这些都是同步异常指令,旨在不同异常等级下进行切换。本文中随机数应用在EL0下运行,处于非安全状态;Linux Kernel在EL1下运行,处于非安全状态;真随机数生成器服务在EL3下运行,处于安全状态。
Linux与BL31之间添加SMC实现随机数获取_第2张图片

参考

  1. Linux hwrng以及ARM TRNG记录
  2. 在QEMU虚拟机和宿主机之间传输文件
  3. SMC Calling Convention (SMCCC)
  4. Trusted Firmware-A

欢迎关注“安全有理”微信公众号。

Linux与BL31之间添加SMC实现随机数获取_第3张图片

你可能感兴趣的:(开发实战,linux,arm开发,安全架构)