Commit 950fd0db authored by Benjamin Gwin's avatar Benjamin Gwin Committed by Greg Kroah-Hartman
Browse files

arm64: kexec_file: try more regions if loading segments fails



[ Upstream commit 108aa503 ]

It's possible that the first region picked for the new kernel will make
it impossible to fit the other segments in the required 32GB window,
especially if we have a very large initrd.

Instead of giving up, we can keep testing other regions for the kernel
until we find one that works.

Suggested-by: default avatarRyan O'Leary <ryanoleary@google.com>
Signed-off-by: default avatarBenjamin Gwin <bgwin@google.com>
Link: https://lore.kernel.org/r/20201103201106.2397844-1-bgwin@google.com


Signed-off-by: default avatarWill Deacon <will@kernel.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent f3c3bb3b
Loading
Loading
Loading
Loading
+31 −10
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ static void *image_load(struct kimage *image,
	u64 flags, value;
	bool be_image, be_kernel;
	struct kexec_buf kbuf;
	unsigned long text_offset;
	unsigned long text_offset, kernel_segment_number;
	struct kexec_segment *kernel_segment;
	int ret;

@@ -88,11 +88,37 @@ static void *image_load(struct kimage *image,
	/* Adjust kernel segment with TEXT_OFFSET */
	kbuf.memsz += text_offset;

	ret = kexec_add_buffer(&kbuf);
	if (ret)
	kernel_segment_number = image->nr_segments;

	/*
	 * The location of the kernel segment may make it impossible to satisfy
	 * the other segment requirements, so we try repeatedly to find a
	 * location that will work.
	 */
	while ((ret = kexec_add_buffer(&kbuf)) == 0) {
		/* Try to load additional data */
		kernel_segment = &image->segment[kernel_segment_number];
		ret = load_other_segments(image, kernel_segment->mem,
					  kernel_segment->memsz, initrd,
					  initrd_len, cmdline);
		if (!ret)
			break;

		/*
		 * We couldn't find space for the other segments; erase the
		 * kernel segment and try the next available hole.
		 */
		image->nr_segments -= 1;
		kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
	}

	if (ret) {
		pr_err("Could not find any suitable kernel location!");
		return ERR_PTR(ret);
	}

	kernel_segment = &image->segment[image->nr_segments - 1];
	kernel_segment = &image->segment[kernel_segment_number];
	kernel_segment->mem += text_offset;
	kernel_segment->memsz -= text_offset;
	image->start = kernel_segment->mem;
@@ -101,12 +127,7 @@ static void *image_load(struct kimage *image,
				kernel_segment->mem, kbuf.bufsz,
				kernel_segment->memsz);

	/* Load additional data */
	ret = load_other_segments(image,
				kernel_segment->mem, kernel_segment->memsz,
				initrd, initrd_len, cmdline);

	return ERR_PTR(ret);
	return 0;
}

#ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG
+8 −1
Original line number Diff line number Diff line
@@ -242,6 +242,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
	return ret;
}

/*
 * Tries to add the initrd and DTB to the image. If it is not possible to find
 * valid locations, this function will undo changes to the image and return non
 * zero.
 */
int load_other_segments(struct kimage *image,
			unsigned long kernel_load_addr,
			unsigned long kernel_size,
@@ -250,7 +255,8 @@ int load_other_segments(struct kimage *image,
{
	struct kexec_buf kbuf;
	void *headers, *dtb = NULL;
	unsigned long headers_sz, initrd_load_addr = 0, dtb_len;
	unsigned long headers_sz, initrd_load_addr = 0, dtb_len,
		      orig_segments = image->nr_segments;
	int ret = 0;

	kbuf.image = image;
@@ -336,6 +342,7 @@ int load_other_segments(struct kimage *image,
	return 0;

out_err:
	image->nr_segments = orig_segments;
	vfree(dtb);
	return ret;
}