PAN机制简介
内核PAN机制(Privileged Access Never)阻止内核态程序直接访问用户态的数据,只能通过内核提供的固定接口copy_from_user,copy_to_user与用户空间交换数据。使用这一机制的原因主要是因为在某些攻击场景下,黑客通过控制用户态数据来执行漏洞利用,比如towelroot的提权POC[1]。
从实现技术上看,PAN通过修改ttbr0_el1寄存器的值(因为ttbr0_el1寄存器保存着一级页表的地址),使内核无法完成对用户态地址的寻址,从而无法操作用户态地址[2]。另外,使能PAN的内核选项CONFIG_ARM64_SW_TTBR0_PAN的字面意思也清晰地表达了PAN的实现原理——通过“switch ttbr0”来实现PAN。
ARM64使能PAN机制
在ARM64(armv8)上通过启用内核配置CONFIG_ARM64_SW_TTBR0_PAN可以启用PAN功能。需要注意的是,不同CPU架构甚至同一架构下不同版本的使能方式可能存在差别,具体可参考下表:
(表格来源:Exploit Methods/Userspace data usage - Linux Kernel Security Subsystem)
如何测试PAN是否生效
PAN机制的测试不同于KASLR,KASLR的测试方法十分简单,通过多次启动系统对比内核符号的地址即可判断KASLR是否生效,但PAN机制没办法通过系统环境直接判断是否生效。我们可以通过编写内核模块的方式测试PAN,具体的测试步骤如下:
第一步:编写内核模块,在内核模块中使用memcpy直接访问用户空间数据。
#define BUFSIZE 100
static int kmem = 20;
module_param(kmem,int,0660);
static struct proc_dir_entry *entry;
static ssize_t panwrite(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int count, len, tmp;
char buf[BUFSIZE];
if(*ppos > 0 || count > BUFSIZE)
return -EFAULT;
memcpy(buf, ubuf, count);
count = sscanf(buf,"%d",&tmp);
if(count != 1)
return -EFAULT;
kmem = tmp;
len = strlen(buf);
*ppos = len;
return len;
}
static const struct proc_ops panops =
{
.proc_read = panread,
.proc_write = panwrite,
};
static int pan_init(void)
{
entry = proc_create("pandev", 0660, NULL, &panops);
printk(KERN_ALERT "hello...\n");
return 0;
}
static void pan_cleanup(void)
{
proc_remove(entry);
printk(KERN_WARNING "bye ...\n");
}
module_init(pan_init);
module_exit(pan_cleanup);
用户态测试程序如下,主要逻辑是通过buf向内核传递参数,修改/proc/pandev的值:
char buf[100];
int fd = open("/proc/pandev", O_RDWR);
read(fd, buf, 100);
puts(buf);
lseek(fd, 0 , SEEK_SET);
write(fd, "33", 5);
lseek(fd, 0 , SEEK_SET);
read(fd, buf, 100);
puts(buf);
第二步:编译模块,在未启用PAN的系统上安装模块并测试。
adas:/home# insmod lkm_hello.ko
[ 31.181093] hello...
adas:/home# cat /proc/pandev
kmem = 20
xcu-s32g274a:/home# ./uspace
kmem = 20
kmem = 33
adas:/home# cat /proc/pandev
kmem = 33
根据测试结果可知,在未开启PAN的系统上,内核通过memcpy的方式可以直接访问用户态的内存数据,下一步我们将用同样的测试case验证使能后的情况。
第三步:在内核配置中使能PAN。
在内核配置中开启CONFIG_ARM64_SW_TTBR0_PAN之后重新编译内核,烧录并启动系统,确认/proc/config.gz文件中CONFIG_ARM64_SW_TTBR0_PAN是否使能。
第四步:再次安装运行测试case。
adas:/home# insmod lkm_hello.ko
adas:/home# [ 39.387532] hello...
adas:/home# ./uspace
[ 90.541095] Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000005589d52f30
[ 90.647970] printk: console [ttyLF0]: printing thread stopped
[ 90.649269] Mem abort info:
[ 90.656813] ESR = 0x96000005
[ 90.660425] EC = 0x25: DABT (current EL), IL = 32 bits
[ 90.665952] SET = 0, FnV = 0
[ 90.669112] EA = 0, S1PTW = 0
[ 90.672367] Data abort info:
[ 90.675341] ISV = 0, ISS = 0x00000005
[ 90.679289] CM = 0, WnR = 0
[ 90.682334] user pgtable: 4k pages, 39-bit VAs, pgdp=0000000082da7000
[ 90.689038] [0000005589d52f30] pgd=00000000970de003, p4d=00000000970de003, pud=00000000970de003, pmd=0000000097020003, pte=00e80000974bdf43
[ 90.701908] Internal error: Oops: 96000005 [#1] PREEMPT_RT SMP
[ 90.707895] Modules linked in: lkm_hello(O) pfeng(O)
[ 90.712994] CPU: 3 PID: 1850 Comm: sh Tainted: G O 5.10.41-rt42+g5c4c385db992 #1
[ 90.721912] Hardware name: XXXXXX (DT)
[ 90.726731] pstate: 80400005 (Nzcv daif +PAN -UAO -TCO BTYPE=--)
[ 90.732887] pc : __memcpy+0x94/0x180
[ 90.736564] lr : mywrite+0x64/0xc4 [lkm_hello]
[ 90.741125] sp : ffffffc01422bcf0
[ 90.744516] x29: ffffffc01422bcf0 x28: ffffff800ad6ea00
[ 90.749962] x27: 0000000000000000 x26: 0000000000000000
[ 90.755406] x25: 0000000000000000 x24: 0000000000000000
[ 90.760851] x23: 0000000000000000 x22: ffffffc01422be30
[ 90.766297] x21: 0000005589d52f30 x20: 0000000000000004
[ 90.771742] x19: ffffffc01422be30 x18: ffffffc010958ec8
[ 90.777187] x17: 0000000000000000 x16: 0000000000000000
[ 90.782630] x15: 0000000000000020 x14: 0000000000000000
[ 90.788074] x13: 0000000000000000 x12: fffffffffffe7687
[ 90.793517] x11: ffffffc010958ee0 x10: ffffffc010960c10
[ 90.798962] x9 : ffffffc01422bcf0 x8 : ffffffc010958ea0
[ 90.804407] x7 : ffffffc01422bb70 x6 : ffffffc01422bd2c
[ 90.809851] x5 : ffffff806fc487b8 x4 : 0000000000000000
[ 90.815297] x3 : 0000000000000027 x2 : 0000000000000004
[ 90.820742] x1 : 0000005589d52f30 x0 : ffffffc01422bd2c
[ 90.826188] Call trace:
[ 90.828693] __memcpy+0x94/0x180
[ 90.832005] proc_reg_write+0xa8/0xec
[ 90.835765] vfs_write+0xf0/0x2b0
[ 90.839164] ksys_write+0x6c/0x100
[ 90.842649] __arm64_sys_write+0x20/0x30
[ 90.846667] el0_svc_common.constprop.0+0x78/0x1a0
[ 90.851582] do_el0_svc+0x24/0x90
[ 90.854978] el0_svc+0x14/0x20
[ 90.858112] el0_sync_handler+0x1a4/0x1b0
[ 90.862219] el0_sync+0x184/0x1c0
[ 90.865625] Code: 36180062 f8408423 f80084c3 36100062 (b8404423)
[ 90.871875] ---[ end trace 0000000000000002 ]---
内核模块安装成功,但是运行用户态程序之后出现崩溃,根据日志“Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000005589d52f30”等信息,我们可以确认PAN机制阻止了内核对用户态内存数据的直接访问。
参考文献
[1].towelroot提权POC
https://github.com/geekben/towelroot/blob/master/towelroot.c
[2].ARM64 PAN的实现
git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux ttbr0-pan