From nobody Thu Nov 20 15:23:36 2025 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4dC2Dn13pGz6HXhm for ; Thu, 20 Nov 2025 15:23:37 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4dC2Dm6yx5z3PKG for ; Thu, 20 Nov 2025 15:23:36 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1763652217; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EeCOulP5kHoBuiRwNdDanaEWehKzqPZPOCrVQFeHdGs=; b=quE2iH1hV49Nl0UEn3NxubcUyg02UFSYMhEIYjM4tULvkbGrzHw3HF3I2U9SNCRql2r/+C ksj749JWpl139knwoSyz7SlQ7WTYirMouSlgD2fzNU1HF0twUwcfz4NAbCaXWaDu9xLL9f TuqE3V/rVasg7l4VAfgWNuscbchXXc2ZNNnRSZE4KHateZWq612UlpkTl1mGI602+NVCby XiGDVeUzUBtte9MOXviNuxRHQ6giYfGLGCvyFQE/a2QoDBqu3YRbaxq2rjzShW6KMUG5fy x49rHssbFGMTquaX7LFksqSdtFya5KdL9GuB4/lGYZH45SuZxQKAnuKTFtkZ0Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1763652217; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EeCOulP5kHoBuiRwNdDanaEWehKzqPZPOCrVQFeHdGs=; b=qupZHJlP2jPcG9vnVY3vLVnC1lRtzNBPFvRQw8O9l6KF0sUBwYXg0G4xu81lFkSggufSMw gmsDUHh7yAz8LsjM9jHupeRDHh/1XThr269xcG96HGzLVEd3ZMrenlsx5UErX/NqKC95GD hf+usg8TC/ojQs/YWFWdatxukaGYWUn9QXl0Hi7kVbo7D8BBu7wQEnsWPOkcKdgxE6PZ9X IXZ5mfDz1735ssSYN8F9P9gQ53YEYQovPCm3jCJZ1Esv71dn95CEGhYGpuRpDY1C87VOnl bEY5iblFiWSF0Lzz9vDv+fRqab2QfeLKEW9Oj6gmv8XZT7qyoE4g1/Ztzcr3xA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1763652217; a=rsa-sha256; cv=none; b=SvsQdNGzN/KRdPcPpuVGGSorr/0GZW4608RuYijWiLL2sDkr9TEnzZUkHN3YLyxCzanB7/ aHvCcuZ+IbNYOv+ZILM+SO2UW7zmeQVa0dNuU7ho1tqYq4if94WsG5M1jCuXD+WQgNwnIi 5h6IJYBjkWlrJoLg8HGEZjX0LGtOr+lJZ2FsfdjJtydi5ilmrtKA69YooP8+CHZ5yhvYci d4IMimhm/AN0fsE6f7K7PPEsdfhEFrsmCdwtvSce8lPopKs1WRZNW2lZlhQokI66Maaarx K0AOqTDKOoevMmGqOkxhwTXzkvJgtKY8TAVqSsvSdPDVIFtBk5gfvm6wA8D63Q== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4dC2Dm6RLRzYlr for ; Thu, 20 Nov 2025 15:23:36 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3693b by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 20 Nov 2025 15:23:36 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Christos Margiolis Subject: git: 253b98f749cf - main - sound: Fix KASSERT panics in chn_read() and chn_write() List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: christos X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 253b98f749cf93a9a682f46925c43cbbd04e1110 Auto-Submitted: auto-generated Date: Thu, 20 Nov 2025 15:23:36 +0000 Message-Id: <691f3278.3693b.53fcd176@gitrepo.freebsd.org> The branch main has been updated by christos: URL: https://cgit.FreeBSD.org/src/commit/?id=253b98f749cf93a9a682f46925c43cbbd04e1110 commit 253b98f749cf93a9a682f46925c43cbbd04e1110 Author: Christos Margiolis AuthorDate: 2025-11-20 15:23:09 +0000 Commit: Christos Margiolis CommitDate: 2025-11-20 15:23:09 +0000 sound: Fix KASSERT panics in chn_read() and chn_write() INVARIANTS kernels may trigger a KASSERT panic from sndbuf_acquire(), when fuzzing write(2) using stress2, because of a race in chn_write(). In the case of chn_write(), what sndbuf_acquire() does is extend the ready-to-read area of the buffer by a specified amount of bytes. The KASSERT in question makes sure the number of bytes we want to extend the ready area by, is less than or equal to the number of free bytes in the buffer. This makes sense, because we cannot extend the ready area to something larger than what is available (i.e., free) in the first place. What chn_write() currently does for every write is; calculate the appropriate write size, let's say X, unlock the channel, uiomove() X bytes to the channel's buffer, lock the channel, and call sndbuf_acquire() to extend the ready area by X bytes. The problem with this approach, however, is the following. Suppose an empty channel buffer with a length of 1024 bytes, and 2 threads, (A) and (B), where (B) is a higher-priority one. Suppose thread (A) wants to write 1024 bytes. It unlocks the channel and uiomove()s 1024 bytes to the channel buffer. At the same time, thread (B) picks up the lock, and because it is higher priority, it keeps dominating the lock for a few iterations. By the time thread (A) picks up the lock again, it tries to call sndbuf_acquire() with a size of 1024 bytes, which was calculated before it performed the uiomove(). In this case, there is a very high chance that the buffer will not be empty, that is, have a free area of 1024 bytes, as was the case when thread (A) started executing, and so the KASSERT will trigger a panic because the condition (bytes <= free) is not met. Another scenario that can trigger a panic is the following: suppose a buffer with a size of 4 bytes, and two threads: (A) and (B). In the first iteration, thread (A) wants to write 2 bytes, while the buffer is empty, BUT the pointer (sndbuf_getfreeptr()) is at the end (i.e., buf[3]). In the first iteration of the loop, because of the way we calculate t, we'll end up writing only 1 byte, so after sz -= t, sz will be 1, and so we'll need one more iteration in the inner loop, to write the remaining 1 byte. Now we're at the end of the first loop, thread (A) unlocks the channel, it has written 1 byte, it needs to write 1 more, and the buffer is left with 3 empty slots. Now thread (B) picks up the lock, and it wants to write 3 (or more) bytes. Eventually it writes the 3 bytes, and it leaves the buffer with 0 free slots. By the time thread (A) picks up the lock again, and continues with the second iteration of the inner loop, it will try to write the last byte, but sndbuf_acquire() will panic because there is no free space anymore. To fix this, get rid of the inner loop and calculate the write size on each iteration. Also, call sndbuf_acquire() before unlocking the channel. In the scenarios explained above, we'll end up entering the chn_sleep() case. Modify it as well, so that we do not kill the channel if we need to sleep more. Do the same for chn_read() to avoid possible similar panics from sndbuf_dispose(). Reported by: pho Tested by: christos, pho Sponsored by: The FreeBSD Foundation MFC after: 1 week Reviewed by: pho, kib Differential Revision: https://reviews.freebsd.org/D53666 --- sys/dev/sound/pcm/channel.c | 70 ++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index f29a819ce0ae..e92181d74e19 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -438,7 +438,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; - int ret, timeout, sz, t, p; + int ret, timeout, sz, p; CHN_LOCKASSERT(c); @@ -446,24 +446,17 @@ chn_write(struct pcm_channel *c, struct uio *buf) timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { + p = sndbuf_getfreeptr(bs); sz = min(buf->uio_resid, sndbuf_getfree(bs)); + sz = min(sz, bs->bufsize - p); if (sz > 0) { - /* - * The following assumes that the free space in - * the buffer can never be less around the - * unlock-uiomove-lock sequence. - */ - while (ret == 0 && sz > 0) { - p = sndbuf_getfreeptr(bs); - t = min(sz, bs->bufsize - p); - off = sndbuf_getbufofs(bs, p); - CHN_UNLOCK(c); - ret = uiomove(off, t, buf); - CHN_LOCK(c); - sz -= t; - sndbuf_acquire(bs, NULL, t); - } - ret = 0; + off = sndbuf_getbufofs(bs, p); + sndbuf_acquire(bs, NULL, sz); + CHN_UNLOCK(c); + ret = uiomove(off, sz, buf); + CHN_LOCK(c); + if (ret != 0) + break; if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) @@ -483,13 +476,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) ret = EAGAIN; } else { ret = chn_sleep(c, timeout); - if (ret == EAGAIN) { - ret = EINVAL; - c->flags |= CHN_F_DEAD; - device_printf(c->dev, "%s(): %s: " - "play interrupt timeout, channel dead\n", - __func__, c->name); - } else if (ret == ERESTART || ret == EINTR) + if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } @@ -552,7 +539,7 @@ chn_read(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; - int ret, timeout, sz, t, p; + int ret, timeout, sz, p; CHN_LOCKASSERT(c); @@ -568,35 +555,22 @@ chn_read(struct pcm_channel *c, struct uio *buf) timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { + p = sndbuf_getreadyptr(bs); sz = min(buf->uio_resid, sndbuf_getready(bs)); + sz = min(sz, bs->bufsize - p); if (sz > 0) { - /* - * The following assumes that the free space in - * the buffer can never be less around the - * unlock-uiomove-lock sequence. - */ - while (ret == 0 && sz > 0) { - p = sndbuf_getreadyptr(bs); - t = min(sz, bs->bufsize - p); - off = sndbuf_getbufofs(bs, p); - CHN_UNLOCK(c); - ret = uiomove(off, t, buf); - CHN_LOCK(c); - sz -= t; - sndbuf_dispose(bs, NULL, t); - } - ret = 0; + off = sndbuf_getbufofs(bs, p); + sndbuf_dispose(bs, NULL, sz); + CHN_UNLOCK(c); + ret = uiomove(off, sz, buf); + CHN_LOCK(c); + if (ret != 0) + break; } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) ret = EAGAIN; else { ret = chn_sleep(c, timeout); - if (ret == EAGAIN) { - ret = EINVAL; - c->flags |= CHN_F_DEAD; - device_printf(c->dev, "%s(): %s: " - "record interrupt timeout, channel dead\n", - __func__, c->name); - } else if (ret == ERESTART || ret == EINTR) + if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } }