又又双叒叕碰到kernel crash了,kernel log里显示“Unable to handle kernel NULL pointer dereference at virtual address 00000018”,提示的很明显,访问空指针了,而且还打印出了 PC is at d_path+0x28/0x1ac 提示,大概率在调d_path函数时触发的,
//不详的征兆:难道第一个参数访问空指针了??d_path( &file->f_path, full_path, MAX_PATH );
已知出现的场景是 insmod ko时,触发crash,之前这部分代码是没问题的(Android10以上,kernel 4.0以上),为什么偏偏在该终端下出现问题(基于Android6,kernel3.10),那么只能看看是否存在什么差异了。
首先查看insmod命令实现,发现是toybox里的,Android6里的源码为如下:
//https://www.androidos.net.cn/android/6.0.1_r16/xref/external/toybox/toys/other/insmod.c 源码位置
void insmod_main(void)
{
char * buf = NULL;
int len, res, i;
int fd = xopen(*toys.optargs, O_RDONLY);
len = fdlength(fd);
buf = xmalloc(len);
xreadall(fd, buf, len);
i = 1;
while(toys.optargs[i] &&
strlen(toybuf) + strlen(toys.optargs[i]) + 2 < sizeof(toybuf))
{
strcat(toybuf, toys.optargs[i++]);
strcat(toybuf, " ");
}
res = init_module(buf, len, toybuf); //可以看到执行insmod时,会调用init_module系统调用
if (CFG_TOYBOX_FREE) {
if (buf != toybuf) free(buf);
close(fd);
}
if (res) perror_exit("failed to load %s", toys.optargs[0]);
}
那么再看下kernel里的init_module实现,
//https://elixir.bootlin.com/linux/v3.10.90/source/kernel/module.c#L3339
/* Sets info->hdr and info->len. */
static int copy_module_from_user(const void __user *umod, unsigned long len,
struct load_info *info)
{
int err;
info->len = len;
if (info->len < sizeof(*(info->hdr)))
return -ENOEXEC;
//发现了端倪,这里为什么传NULL呢????这的代码根本没意义啊
err = security_kernel_module_from_file(NULL);
if (err)
return err;
/* Suck in entire file: we'll want most of it. */
info->hdr = vmalloc(info->len);
if (!info->hdr)
return -ENOMEM;
if (copy_from_user(info->hdr, umod, info->len) != 0) {
vfree(info->hdr);
return -EFAULT;
}
return 0;
}
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs);
err = copy_module_from_user(umod, len, &info);
if (err)
return err;
return load_module(&info, uargs, 0);
}
kernel源码里 传的NULL,crash处的代码因为hook了kernel_module_from_file函数,
加上自己的代码里没有做好空指针检测,所以直接触发了crash。
查看最新6.12内核里的源码,发现已经重新实现了该部分逻辑。
//https://elixir.bootlin.com/linux/v6.12-rc6/source/kernel/module/main.c#L1961 源码位置
/* Sets info->hdr and info->len. */
static int copy_module_from_user(const void __user *umod, unsigned long len,
struct load_info *info)
{
int err;
info->len = len;
if (info->len < sizeof(*(info->hdr)))
return -ENOEXEC;
err = security_kernel_load_data(LOADING_MODULE, true);
if (err)
return err;
/* Suck in entire file: we'll want most of it. */
info->hdr = __vmalloc(info->len, GFP_KERNEL | __GFP_NOWARN);
if (!info->hdr)
return -ENOMEM;
if (copy_chunked_from_user(info->hdr, umod, info->len) != 0) {
err = -EFAULT;
goto out;
}
err = security_kernel_post_load_data((char *)info->hdr, info->len,
LOADING_MODULE, "init_module");
out:
if (err)
vfree(info->hdr);
return err;
}
看来引入了新的逻辑,security_kernel_post_load_data ,查看下kernel commit log
其中提到了
With this change, LSMs can gain coverage over non-file-backed data loads (e.g. init_module(2) and firmware userspace helper), which will happen in subsequent patches.
也就是说,从引入这个新函数之后,LSM才有了对init_module non-file-backed data场景下的覆盖?
那还有个疑问,为什么之前没有出现crash呢?查看下Android10版本以上的toybox源码,发现
//https://www.androidos.net.cn/android/10.0.0_r6/xref/external/toybox/toys/other/insmod.c 源码位置
void insmod_main(void)
{
int fd = xopenro(*toys.optargs);
int i, rc;
i = 1;
while (toys.optargs[i] &&
strlen(toybuf) + strlen(toys.optargs[i]) + 2 < sizeof(toybuf))
{
strcat(toybuf, toys.optargs[i++]);
strcat(toybuf, " ");
}
// finit_module was new in Linux 3.8, and doesn't work on stdin,
// so we fall back to init_module if necessary.
//好吧,这里说的很清楚了,逻辑为会先尝试finit_module,不成功,再尝试init_module,看来之前触发的都是finit_module逻辑
rc = finit_module(fd, toybuf, 0);
if (rc && (fd == 0 || errno == ENOSYS)) {
off_t len = 0;
char *path = !strcmp(*toys.optargs, "-") ? "/dev/stdin" : *toys.optargs;
char *buf = readfileat(AT_FDCWD, path, NULL, &len);
rc = init_module(buf, len, toybuf);
if (CFG_TOYBOX_FREE) free(buf);
}
if (rc) perror_exit("failed to load %s", toys.optargs[0]);
if (CFG_TOYBOX_FREE) close(fd);
}
既然这样那就只能加上非空检测健壮自己的代码了。确实令人崩溃啊。。