Bug 2097387 (CVE-2022-32981) - CVE-2022-32981 kernel: Linux kernel for powerpc 32-bit buffer overflow in ptrace PEEKUSER/POKEUSER
Summary: CVE-2022-32981 kernel: Linux kernel for powerpc 32-bit buffer overflow in ptr...
Keywords:
Status: CLOSED NOTABUG
Alias: CVE-2022-32981
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Red Hat Product Security
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks: 2097388
TreeView+ depends on / blocked
 
Reported: 2022-06-15 15:02 UTC by Pedro Sampaio
Modified: 2023-03-14 18:19 UTC (History)
48 users (show)

Fixed In Version:
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
Environment:
Last Closed: 2022-11-07 05:28:09 UTC
Embargoed:


Attachments (Terms of Use)

Description Pedro Sampaio 2022-06-15 15:02:59 UTC
The Linux kernel for powerpc 32-bit has a buffer overflow in the handling of ptrace
PEEKUSER/POKEUSER when accessing floating point registers.

The fix for mainline is:
  https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/commit/?id=8e1278444446fc97778a5e5c99bca1ce0bbc5ec9

Which is included in v5.19-rc2.

Stable backports have been posted.

A test case is included below, it will report if the system is correctly
patched, it is safe to run on an unpatched system.

cheers

---
#undef NDEBUG
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

static double expected = 0.123456L;

static int child(int shm_id)
{
	int *cptr = shmat(shm_id, NULL, 0);

	asm volatile (
		"lfd	 %%f0, 0(%0)	;"
		"lfd	 %%f1, 0(%0)	;"
		"li 	 %%r9, 1	;"
		"stw	 %%r9, 0(%1)	;"
		"1:"
		"lwz	 %%r9, 0(%2)	;"
		"cmpwi	 %%r9, 0	;"
		"beq	   1b		;"
		: // outputs
		: // inputs
		  "b" (&expected), "b" (&cptr[1]), "b" (&cptr[0])
		: // clobbers
		  "memory", "r9", "fr0", "fr1"
	);

	return 0;
}

int start_trace(pid_t child)
{
	int ret;

	ret = ptrace(PTRACE_ATTACH, child, NULL, NULL);
	if (ret) {
		perror("ptrace(PTRACE_ATTACH) failed");
		return -1;
	}
	ret = waitpid(child, NULL, 0);
	if (ret != child) {
		perror("waitpid() failed");
		return -1;
	}
	return 0;
}

int stop_trace(pid_t child)
{
	int ret;

	ret = ptrace(PTRACE_DETACH, child, NULL, NULL);
	if (ret) {
		perror("ptrace(PTRACE_DETACH) failed");
		return -1;
	}
	return 0;
}

long raw_ptrace(enum __ptrace_request request, pid_t pid, unsigned long addr, void *data)
{
	return syscall(__NR_ptrace, request, pid, (void *)addr, data);
}

#define PEEKS_PER_FPR	(sizeof(__u64) / sizeof(unsigned long))

int peek_fpr(pid_t child, int frnum, __u64 *fpr)
{
	unsigned long *p, addr;
	int i, fpindex;
	long ret;

	fpindex = PEEKS_PER_FPR * frnum;

	p = (unsigned long *)fpr;
	for (i = 0; i < PEEKS_PER_FPR; i++, p++) {
		addr = sizeof(unsigned long) * (PT_FPR0 + fpindex + i);
		ret = raw_ptrace(PTRACE_PEEKUSER, child, addr, p);
		if (ret) {
			perror("ptrace(PTRACE_PEEKUSR) failed");
			return -1;
		}
	}

	return 0;
}

int parent(pid_t child)
{
	double f0, f1;

	assert(start_trace(child) == 0);

	assert(peek_fpr(child, 0, (__u64 *)&f0) == 0);
	assert(peek_fpr(child, 1, (__u64 *)&f1) == 0);

	assert(stop_trace(child) == 0);

	printf("expected = %e\n", f0);
	printf("f0       = %e\n", f0);
	printf("f1       = %e\n", f1);

	if (f0 != expected || f1 != expected) {
		printf("FAIL - values don't match! Kernel is buggy.\n");
		return -1;
	}

	printf("OK - values match\n");

	return 0;
}

int main(void)
{
	int shm_id, ret, status, *pptr;
	pid_t pid;

	shm_id = shmget(IPC_PRIVATE, sizeof(int) * 2, 0777|IPC_CREAT);
	assert(shm_id != -1);

	pid = fork();
	assert(pid >= 0);

	if (pid == 0)
		exit(child(shm_id));

	pptr = shmat(shm_id, NULL, 0);

	// Wait for child to signal us to continue
	while (!pptr[1])
		asm volatile("" : : : "memory");

	ret = parent(pid);
	if (ret) {
		kill(pid, SIGTERM);
		shmdt((void *)pptr);
		shmctl(shm_id, IPC_RMID, NULL);
		return -1;
	}

	// Signal child to exit
	pptr[0] = 1;
	shmdt((void *)pptr);

	ret = wait(&status);
	shmctl(shm_id, IPC_RMID, NULL);

	assert(ret != -1 && WIFEXITED(status) && !WEXITSTATUS(status));

	return 0;
}


Note You need to log in before you can comment on or make changes to this bug.