In aosp/1979327 we attempted to prevent tasks with pending signals and PF_FREEZER_SKIP from being immediately rescheduled, because such tasks would crash the kernel if run while no capable CPUs were online. This was implemented by declining to immediately reschedule them unless various conditions were met. However, this ended up causing signals to fail to be delivered if the signal was received while a task is processing a syscall, such as futex(2), that will block with PF_FREEZER_SKIP set, as the kernel relies on a check for TIF_SIGPENDING after setting the task state to TASK_INTERRUPTIBLE in order to deliver such a signal. This patch is an alternative solution to the original problem that avoids introducing the signal delivery bug. It works by changing how freezer_should_skip() is implemented. Instead of just checking PF_FREEZER_SKIP, we also use the on_rq field to check whether the task is not on a runqueue. In this way we ensure that a task that will be immediately rescheduled will not return true from freezer_should_skip(), and the task will block the freezer unless it is actually taken off the runqueue. Signed-off-by: Peter Collingbourne <pcc@google.com> Bug: 202918514 Bug: 251700836 Change-Id: I3f9b705ce9ad2ca1d2df959f43cf05bef78560f8
143 lines
3.4 KiB
C
143 lines
3.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/unistd.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/minix_fs.h>
|
|
#include <linux/romfs_fs.h>
|
|
#include <linux/initrd.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/kmod.h>
|
|
#include <uapi/linux/mount.h>
|
|
|
|
#include "do_mounts.h"
|
|
|
|
unsigned long initrd_start, initrd_end;
|
|
int initrd_below_start_ok;
|
|
unsigned int real_root_dev; /* do_proc_dointvec cannot handle kdev_t */
|
|
static int __initdata mount_initrd = 1;
|
|
|
|
phys_addr_t phys_initrd_start __initdata;
|
|
unsigned long phys_initrd_size __initdata;
|
|
|
|
static int __init no_initrd(char *str)
|
|
{
|
|
mount_initrd = 0;
|
|
return 1;
|
|
}
|
|
|
|
__setup("noinitrd", no_initrd);
|
|
|
|
static int __init early_initrdmem(char *p)
|
|
{
|
|
phys_addr_t start;
|
|
unsigned long size;
|
|
char *endp;
|
|
|
|
start = memparse(p, &endp);
|
|
if (*endp == ',') {
|
|
size = memparse(endp + 1, NULL);
|
|
|
|
phys_initrd_start = start;
|
|
phys_initrd_size = size;
|
|
}
|
|
return 0;
|
|
}
|
|
early_param("initrdmem", early_initrdmem);
|
|
|
|
static int __init early_initrd(char *p)
|
|
{
|
|
return early_initrdmem(p);
|
|
}
|
|
early_param("initrd", early_initrd);
|
|
|
|
static int __init init_linuxrc(struct subprocess_info *info, struct cred *new)
|
|
{
|
|
ksys_unshare(CLONE_FS | CLONE_FILES);
|
|
console_on_rootfs();
|
|
/* move initrd over / and chdir/chroot in initrd root */
|
|
init_chdir("/root");
|
|
init_mount(".", "/", NULL, MS_MOVE, NULL);
|
|
init_chroot(".");
|
|
ksys_setsid();
|
|
return 0;
|
|
}
|
|
|
|
static void __init handle_initrd(void)
|
|
{
|
|
struct subprocess_info *info;
|
|
static char *argv[] = { "linuxrc", NULL, };
|
|
extern char *envp_init[];
|
|
int error;
|
|
|
|
pr_warn("using deprecated initrd support, will be removed in 2021.\n");
|
|
|
|
real_root_dev = new_encode_dev(ROOT_DEV);
|
|
create_dev("/dev/root.old", Root_RAM0);
|
|
/* mount initrd on rootfs' /root */
|
|
mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
|
|
init_mkdir("/old", 0700);
|
|
init_chdir("/old");
|
|
|
|
/*
|
|
* In case that a resume from disk is carried out by linuxrc or one of
|
|
* its children, we need to tell the freezer not to wait for us.
|
|
*/
|
|
freezer_do_not_count();
|
|
|
|
info = call_usermodehelper_setup("/linuxrc", argv, envp_init,
|
|
GFP_KERNEL, init_linuxrc, NULL, NULL);
|
|
if (!info)
|
|
return;
|
|
call_usermodehelper_exec(info, UMH_WAIT_PROC);
|
|
|
|
current->flags &= ~PF_FREEZER_SKIP;
|
|
|
|
/* move initrd to rootfs' /old */
|
|
init_mount("..", ".", NULL, MS_MOVE, NULL);
|
|
/* switch root and cwd back to / of rootfs */
|
|
init_chroot("..");
|
|
|
|
if (new_decode_dev(real_root_dev) == Root_RAM0) {
|
|
init_chdir("/old");
|
|
return;
|
|
}
|
|
|
|
init_chdir("/");
|
|
ROOT_DEV = new_decode_dev(real_root_dev);
|
|
mount_root();
|
|
|
|
printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
|
|
error = init_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
|
|
if (!error)
|
|
printk("okay\n");
|
|
else {
|
|
if (error == -ENOENT)
|
|
printk("/initrd does not exist. Ignored.\n");
|
|
else
|
|
printk("failed\n");
|
|
printk(KERN_NOTICE "Unmounting old root\n");
|
|
init_umount("/old", MNT_DETACH);
|
|
}
|
|
}
|
|
|
|
bool __init initrd_load(void)
|
|
{
|
|
if (mount_initrd) {
|
|
create_dev("/dev/ram", Root_RAM0);
|
|
/*
|
|
* Load the initrd data into /dev/ram0. Execute it as initrd
|
|
* unless /dev/ram0 is supposed to be our actual root device,
|
|
* in that case the ram disk is just set up here, and gets
|
|
* mounted in the normal path.
|
|
*/
|
|
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
|
|
init_unlink("/initrd.image");
|
|
handle_initrd();
|
|
return true;
|
|
}
|
|
}
|
|
init_unlink("/initrd.image");
|
|
return false;
|
|
}
|