From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qv1-f42.google.com (mail-qv1-f42.google.com [209.85.219.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 967D92D3225 for ; Mon, 22 Jun 2026 12:48:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.42 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782132509; cv=none; b=le43i7xRCMDG1uYcnkWVVhzZkz3943l05DLDh959/t5ZhpV1cPCaJWv6nBhVE2wW2iMLmZ/QwpoqzsZBnYgRC03cacD4rormXahlVDciCOUNuMI7Qg3iE9hPSYRE3KlK9wJQUzg/V99Pr8vAWzI+32eN3ltUvUGab8agV7YLAXk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782132509; c=relaxed/simple; bh=Flvp6H0PHbcB27ujcsP/jPNSZTCunEsinrLVJAuqJOo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=hn/+AmMNvCale7ZyVAAXTjtswiRqSlOPUzG4psnVm4/z4K3ah2Dh4t7jGbScGXimmLGbL7NeLxtV00zTPjooX/vQrUV6ziKMSiY8csI7NfxiIyZs4WtemkNBMkHFeIPaAUycYT3O0vnUQeZfD5bpS+lE5qqIn/4Ij3b611k41iw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=r7Ua/oPk; arc=none smtp.client-ip=209.85.219.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="r7Ua/oPk" Received: by mail-qv1-f42.google.com with SMTP id 6a1803df08f44-8de7bfa31d5so47809906d6.1 for ; Mon, 22 Jun 2026 05:48:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782132507; x=1782737307; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=AqRTg9jOX9Gjt6m176ot31Vs2qjsZUYGYEsU/aFytIM=; b=r7Ua/oPkd2nO0QloildhCbVAxOS7xa2rejEYVzf9XnslyeV+cYrrwopRWv6lWNCZtL WCMcETs8dgdJb0Feg5mAP2Til720Kwd7NVgEzEdXC/Bz0PSe7ATjZUVlcLjVWhqw+mLN b6NsHvbqqiwwO7+/DliRE0jGIHn5oywdl8FN93AkU6xDW2qP+jNyIjSGUwsdp8476tAb Y4aG4nkaSukDoQyTinw/TzDM0f8rLG4L2j1orBawq5o9vjfVKFgBqSQ4CT+zObbqtABI frtQhcOCYcyJ92BWcJjUdKq+T5tWV47bPExYvVcSRDQi0a0yAz0IXtTIFq46IhGrjL5+ xiAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782132507; x=1782737307; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=AqRTg9jOX9Gjt6m176ot31Vs2qjsZUYGYEsU/aFytIM=; b=EvlzrxUCjJXRcsdS+otLjERYdY62H5/OS33T/oKjxtqA0BtIJuPxFTw8o4vQVv+HJK v9Rk5qLOPT56gJkh+RaAzmEKCbQNCiqzu/jf4/9umL2el/QsIlToms6WAUr+ooi89+Ht zSyvRZV49uZd0x9tyPXHED/YpZ2wyIF512i48A5obV5tybAFp1C9NU5VR7MOv55MNZu9 7UzIt94VZ+qP+5ig4DhNLcyNcXVeDC/4C7O618wKz24HsoRhuRI14mJisalfasfLlZbo sWBoHkoJ+dDWBtYdK/JtVjSGKrN8R2D36FHSMYfF6MlFWn9bvmaQpnx7rfnrPegI1kyc bqAA== X-Forwarded-Encrypted: i=1; AHgh+Rq1N3sl0vkJiNZ25/ADMU2X54rEw5uf2f9bkPR22CSRyAenNYz1dGL7qL3/z09nFyRpGKc4zRUab/Bcax8=@vger.kernel.org X-Gm-Message-State: AOJu0YylLRDBj3uWhgnPQFP74lMJlRT36NEGPH+FW81n1azgxM7z/Mp8 NAPzrFml0KaoklKdZaSKwziGfeZpPoxJjrrbeborX2y9qMLk69zjLLAr X-Gm-Gg: AfdE7cmhr+6Ynqa6tTNLcrn0Ga3fPlbq1UhsxwUVYHelQuEP+/4i2V2ZS4fLwLTbk+4 g7SVOfxSub+4jgwBuaNwz4GmEPFPf3DxrvtS3Rb5ei042cgqWl+oeF/cGJS+DqhN41+X9SxyIad QIudZdaEwmmp7danpSXLUIzfRqDYyFM6rIo025UfM1oL/VZp/EVJu0YX3nuaiU2mh5C1BYhaS37 BkSdw7NaXZOGtMYgZ5uBvYe3wqoSZX4vV+yC/HA0hwqRtC8Tkg9DL5yy7N9awk1zs//LteimytP RLoaSqU/ZFX9ha2AWNyNZ7usMkaxJ1g8xnN+Yhftexlk9HrutY3ibj2z2xxBXCoAzGe6belyHKC U53ep7UtytQy0pgkbx4XSPSkBglpH/FFZqkiyfi/daaGBWnAJ6Of1jKdet2nB7M4rl9YCH2TV1w lB8Z+sdf8I28157Zxz3RRfUV+9ILxvjKXqFySnQxblo0lVItg97oSfbgaDq/SrzERoIO5gUor4u cX+ggr63BRQQfJ1cBkVLQPQUz6Kn8ax X-Received: by 2002:a05:6214:500b:b0:8d2:f76d:690c with SMTP id 6a1803df08f44-8de419b4057mr268057586d6.29.1782132507535; Mon, 22 Jun 2026 05:48:27 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8df82692bddsm93099486d6.43.2026.06.22.05.48.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 22 Jun 2026 05:48:26 -0700 (PDT) From: Michael Bommarito To: Richard Weinberger , Anton Ivanov , Johannes Berg Cc: linux-um@lists.infradead.org, linux-kernel@vger.kernel.org Subject: [PATCH] um: ubd: validate COW header fields before use Date: Mon, 22 Jun 2026 08:48:23 -0400 Message-ID: <20260622124823.1695944-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 7bit read_cow_header() copies the backing-file path and computes geometry from header fields without validating them, and the historical "XXX Need to sanity-check the values read from the header" comment was never addressed. Three image-controlled fields are unsafe: - backing_file[]: not guaranteed NUL-terminated. cow_strdup() and the later strlen()/printf() walk off the end of the header buffer when a crafted image leaves the array unterminated (heap over-read). - sectorsize: used as a divisor in cow_sizes() (bitmap_len = (size + sectorsize - 1) / (8 * sectorsize)); a zero or negative sectorsize causes a divide-by-zero / bogus geometry. - For the V3/V3_b layouts the existing "align == 0" check printed a warning but fell through and then computed ROUND_UP(sizeof(header), 0), another divide-by-zero. Reject an unterminated backing_file for each header version, turn the align == 0 warnings into hard errors (goto out), and reject a non-positive sectorsize before the geometry math runs. A COW image is parsed from a file the guest opens (ubdN=cow,backing on the command line, or a cow file an unprivileged user can point ubd at), so a crafted header is attacker-reachable. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito --- Reproduced on a UML KASAN build (ARCH=um) off this base, driving ubd with crafted COW images. Stock: an unterminated v2 backing_file over-reads the header buffer (KASAN slab-out-of-bounds read in the strdup/strlen path); a sectorsize=0 image and a v3 alignment=0 image each hit a divide error in the cow_sizes()/ROUND_UP geometry. Patched: each crafted image is rejected in read_cow_header() ('not terminated' / 'invalid sectorsize' / 'align == 0') and the driver continues. Benign control: a valid COW image opens cleanly on both stock and patched. Before/after logs available on request. arch/um/drivers/cow_user.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/arch/um/drivers/cow_user.c b/arch/um/drivers/cow_user.c index dc1d1bcd85ec2..bb9f05ac70cf4 100644 --- a/arch/um/drivers/cow_user.c +++ b/arch/um/drivers/cow_user.c @@ -279,7 +279,7 @@ int file_reader(__u64 offset, char *buf, int len, void *arg) return pread(fd, buf, len, offset); } -/* XXX Need to sanity-check the values read from the header */ +/* Sanity checks are performed after reading each header version below. */ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, __u32 *version_out, char **backing_file_out, @@ -325,6 +325,12 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, *sectorsize_out = header->v1.sectorsize; *bitmap_offset_out = sizeof(header->v1); *align_out = *sectorsize_out; + if (!memchr(header->v1.backing_file, '\0', + sizeof(header->v1.backing_file))) { + cow_printf("%s - V1 backing_file not terminated\n", + __func__); + goto out; + } file = header->v1.backing_file; } else if (version == 2) { @@ -338,6 +344,12 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, *sectorsize_out = be32toh(header->v2.sectorsize); *bitmap_offset_out = sizeof(header->v2); *align_out = *sectorsize_out; + if (!memchr(header->v2.backing_file, '\0', + sizeof(header->v2.backing_file))) { + cow_printf("%s - V2 backing_file not terminated\n", + __func__); + goto out; + } file = header->v2.backing_file; } /* This is very subtle - see above at union cow_header definition */ @@ -354,8 +366,15 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, if (*align_out == 0) { cow_printf("read_cow_header - invalid COW header, " "align == 0\n"); + goto out; } *bitmap_offset_out = ROUND_UP(sizeof(header->v3), *align_out); + if (!memchr(header->v3.backing_file, '\0', + sizeof(header->v3.backing_file))) { + cow_printf("%s - V3 backing_file not terminated\n", + __func__); + goto out; + } file = header->v3.backing_file; } else if (version == 3) { @@ -385,14 +404,27 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, if (*align_out == 0) { cow_printf("read_cow_header - invalid COW header, " "align == 0\n"); + goto out; } *bitmap_offset_out = ROUND_UP(sizeof(header->v3_b), *align_out); + if (!memchr(header->v3_b.backing_file, '\0', + sizeof(header->v3_b.backing_file))) { + cow_printf("%s - V3 backing_file not terminated\n", + __func__); + goto out; + } file = header->v3_b.backing_file; } else { cow_printf("read_cow_header - invalid COW version\n"); goto out; } + + if (*sectorsize_out <= 0) { + cow_printf("%s - invalid sectorsize %d\n", __func__, + *sectorsize_out); + goto out; + } err = -ENOMEM; *backing_file_out = cow_strdup(file); if (*backing_file_out == NULL) { base-commit: ef0c9f75a19532d7675384708fc8621e10850104 -- 2.53.0