Bug 2097387 (CVE-2022-32981)

Summary: CVE-2022-32981 kernel: Linux kernel for powerpc 32-bit buffer overflow in ptrace PEEKUSER/POKEUSER
Product: [Other] Security Response Reporter: Pedro Sampaio <psampaio>
Component: vulnerabilityAssignee: Red Hat Product Security <security-response-team>
Status: CLOSED NOTABUG QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: acaringi, adscvr, airlied, alciregi, bhu, bskeggs, chwhite, crwood, ddepaula, dhoward, dvlasenk, fhrbata, hdegoede, hkrzesin, hpa, jarod, jarodwilson, jburrell, jfaracco, jferlan, jforbes, jglisse, jlelli, joe.lawrence, jonathan, josef, jshortt, jstancek, jwboyer, jwyatt, kcarcia, kernel-maint, kernel-mgr, lgoncalv, linville, lzampier, masami256, mchehab, nmurray, ptalbert, qzhao, rvrbovsk, scweaver, steved, tyberry, vkumar, walters, williams
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2022-11-07 05:28:09 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On:    
Bug Blocks: 2097388    

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;
}