Subject: [PATCH AUTOSEL 6.18] io_uring/rw: free potentially allocated iovec on cache put failure

From: Sasha Levin <[email-masked]>
Date: Mon, 2 Feb 2026 16:45:58 -0500

From: Jens Axboe <[email-masked]>

[ Upstream commit 4b9748055457ac3a0710bf210c229d01ea1b01b9 ]

If a read/write request goes through io_req_rw_cleanup() and has an
allocated iovec attached and fails to put to the rw_cache, then it may
end up with an unaccounted iovec pointer. Have io_rw_recycle() return
whether it recycled the request or not, and use that to gauge whether to
free a potential iovec or not.

Reviewed-by: Nitesh Shetty <[email-masked]>
Signed-off-by: Jens Axboe <[email-masked]>
Signed-off-by: Sasha Levin <[email-masked]>
---

LLM Generated explanations, may be completely bogus:

Now let's understand the scope. This commit fix is for a memory leak bug
in the io_uring rw (read/write) path. Let me summarize my analysis:

## Analysis Summary

### 1. COMMIT MESSAGE ANALYSIS
- **Subject**: "io_uring/rw: free potentially allocated iovec on cache
  put failure"
- **Keywords**: "free", "allocated", "failure" - this is clearly a bug
  fix for a resource leak
- **Description**: When a read/write request goes through
  io_req_rw_cleanup() with an allocated iovec and fails to put to the
  rw_cache, the iovec pointer becomes unaccounted (leaked).
- **Reviewed-by**: Nitesh Shetty (Samsung) - indicates review
- **Author**: Jens Axboe (io_uring maintainer)

### 2. CODE CHANGE ANALYSIS

The bug is in `io_rw_recycle()` and `io_req_rw_cleanup()`:

**Before the fix:**
```c
static void io_rw_recycle(struct io_kiocb *req, unsigned int
issue_flags)
{
    struct io_async_rw *rw = req->async_data;

    if (unlikely(issue_flags & IO_URING_F_UNLOCKED))
        return;  // Early return - iovec potentially leaked

    io_alloc_cache_vec_kasan(&rw->vec);
    if (rw->vec.nr > IO_VEC_CACHE_SOFT_CAP)
        io_vec_free(&rw->vec);

    if (io_alloc_cache_put(&req->ctx->rw_cache, rw))
        io_req_async_data_clear(req, 0);
    // PROBLEM: If io_alloc_cache_put fails (returns false when cache is
full),
    // the rw structure is NOT freed and NOT put back into the cache,
    // but the iovec inside rw->vec is also not freed!
}
```

**After the fix:**
```c
static bool io_rw_recycle(struct io_kiocb *req, unsigned int
issue_flags)
{
    // Returns bool to indicate if recycling succeeded

    if (unlikely(issue_flags & IO_URING_F_UNLOCKED))
        return false;  // Caller knows recycling failed

    // ... same cleanup ...

    if (io_alloc_cache_put(&req->ctx->rw_cache, rw)) {
        io_req_async_data_clear(req, 0);
        return true;  // Successfully recycled
    }
    return false;  // Failed to recycle - caller will free
}

static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int
issue_flags)
{
    if (!(req->flags & (REQ_F_REISSUE | REQ_F_REFCOUNT))) {
        req->flags &= ~REQ_F_NEED_CLEANUP;
        if (!io_rw_recycle(req, issue_flags)) {
            struct io_async_rw *rw = req->async_data;
            io_vec_free(&rw->vec);  // FIX: Free the iovec if recycle
failed
        }
    }
}
```

### 3. CLASSIFICATION
- **Type**: Bug fix - memory leak
- **Category**: Resource leak in io_uring read/write path
- **Severity**: Medium - leads to memory leak over time with repeated
  I/O operations when cache is full

### 4. SCOPE AND RISK ASSESSMENT
- **Lines changed**: 11 insertions, 4 deletions (small)
- **Files touched**: 1 (io_uring/rw.c)
- **Complexity**: Low - simple return value change and conditional free
- **Risk**: Very low - the fix is straightforward and localized

### 5. USER IMPACT
- **Who is affected**: All io_uring users doing read/write operations
- **How severe**: Memory leak - can cause gradual memory exhaustion on
  systems with heavy io_uring usage
- **Trigger conditions**: When the rw_cache is full (reaches
  IO_ALLOC_CACHE_MAX=128 entries) and a request cleanup occurs

### 6. STABILITY INDICATORS
- **Reviewed-by**: Yes (Nitesh Shetty)
- **Maintainer authored**: Yes (Jens Axboe is the io_uring maintainer)
- **Testing**: The fix is straightforward and has a Reviewed-by tag

### 7. DEPENDENCY CHECK
- The code being fixed was introduced in v6.14-rc1 (commit
  9ac273ae3dc29)
- The `io_vec_free` function exists in v6.15+
- This fix applies cleanly to 6.14+ and 6.15+ stable branches

### Conclusion

This commit should be backported because:

1. **Fixes a real bug**: Memory leak when io_alloc_cache_put fails
2. **Small and surgical**: Only 15 lines changed in one file
3. **Obviously correct**: The fix simply ensures proper cleanup when
   cache insertion fails
4. **No new features**: Just fixes the existing resource management
5. **Low risk**: The change is well-contained and logical
6. **Affects production code**: io_uring is widely used, and memory
   leaks in this path affect real users

The fix is simple: change `io_rw_recycle()` to return a boolean
indicating success/failure, and have `io_req_rw_cleanup()` call
`io_vec_free()` when recycling fails. This ensures the iovec is always
properly freed.

**YES**

 io_uring/rw.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/io_uring/rw.c b/io_uring/rw.c
index abe68ba9c9dc8..d7388a4a3ea5e 100644
--- a/io_uring/rw.c
+++ b/io_uring/rw.c
@@ -144,19 +144,22 @@ static inline int io_import_rw_buffer(int rw, struct io_kiocb *req,
 	return 0;
 }
 
-static void io_rw_recycle(struct io_kiocb *req, unsigned int issue_flags)
+static bool io_rw_recycle(struct io_kiocb *req, unsigned int issue_flags)
 {
 	struct io_async_rw *rw = req->async_data;
 
 	if (unlikely(issue_flags & IO_URING_F_UNLOCKED))
-		return;
+		return false;
 
 	io_alloc_cache_vec_kasan(&rw->vec);
 	if (rw->vec.nr > IO_VEC_CACHE_SOFT_CAP)
 		io_vec_free(&rw->vec);
 
-	if (io_alloc_cache_put(&req->ctx->rw_cache, rw))
+	if (io_alloc_cache_put(&req->ctx->rw_cache, rw)) {
 		io_req_async_data_clear(req, 0);
+		return true;
+	}
+	return false;
 }
 
 static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int issue_flags)
@@ -190,7 +193,11 @@ static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int issue_flags)
 	 */
 	if (!(req->flags & (REQ_F_REISSUE | REQ_F_REFCOUNT))) {
 		req->flags &= ~REQ_F_NEED_CLEANUP;
-		io_rw_recycle(req, issue_flags);
+		if (!io_rw_recycle(req, issue_flags)) {
+			struct io_async_rw *rw = req->async_data;
+
+			io_vec_free(&rw->vec);
+		}
 	}
 }
 
-- 
2.51.0

---

From: Sasha Levin <[email-masked]>
Date: Mon, 2 Feb 2026 16:45:59 -0500

From: Martin Hamilton <[email-masked]>

[ Upstream commit 64e0924ed3b446fdd758dfab582e0e961863a116 ]

The amp/speakers on the Lenovo Yoga Book 9i 13IRU8 laptop aren't
fully powered up, resulting in horrible tinny sound by default.

The kernel has an existing quirk for PCI SSID 0x17aa3843 which
matches this machine and several others. The quirk applies the
ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP fixup, however the fixup does not
work on this machine.

This patch modifies the existing quirk by adding a check for the
subsystem ID 0x17aa3881. If present, ALC287_FIXUP_TAS2781_I2C will
be applied instead of ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP. With this
change the TAS2781 amp is powered up, firmware is downloaded and
recognised by HDA/SOF - i.e. all is good, and we can boogie.

Code is re-used from alc298_fixup_lenovo_c940_duet7(), which fixes a
similar problem with two other Lenovo laptops.

Cross checked against ALSA cardinfo database for potential clashes.
Tested against 6.18.5 kernel built with Arch Linux default options.
Tested in HDA mode and SOF mode.

Note: Possible further work required to address quality of life issues
caused by the firmware's agressive power saving, and to improve ALSA
control mappings.

Signed-off-by: Martin Hamilton <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Takashi Iwai <[email-masked]>
Signed-off-by: Sasha Levin <[email-masked]>
---

The `alc298_fixup_lenovo_c940_duet7` pattern was introduced in June
2022, making it well-established and available in all active stable
trees.

## Summary

### What the commit fixes:
The Lenovo Yoga Book 9i 13IRU8 laptop has broken audio - "horrible tinny
sound" - because the TAS2781 amplifier isn't being properly initialized.
The machine shares a PCI SSID (0x17aa3843) with other Yoga 9i models,
but needs a different fixup to work correctly.

### Why it matters to stable users:
This is a real-world hardware issue affecting laptop users who have
completely broken speaker audio. The "tinny sound" described means the
speakers are essentially unusable.

### Does it meet stable kernel rules?

1. **Obviously correct**: Yes - follows an identical, proven pattern
   from `alc298_fixup_lenovo_c940_duet7()` that has been in the kernel
   since June 2022
2. **Fixes a real bug**: Yes - "horrible tinny sound" is a significant
   audio bug on production hardware
3. **Fixes an important issue**: Yes - broken speakers on a premium
   laptop significantly impact usability
4. **Small and contained**: Yes - ~25 lines, single file, minimal scope
5. **No new features**: Correct - it routes an existing machine to an
   existing fixup (`ALC287_FIXUP_TAS2781_I2C`)
6. **Applies cleanly**: Should apply cleanly as it only touches quirk
   infrastructure

### Risk vs Benefit:
- **Benefit**: High - fixes broken audio for Yoga Book 9i users
- **Risk**: Very low - the check for subsystem ID 0x17aa3881 ensures
  only the specific machine is affected. Other Yoga 9i models with SSID
  0x17aa3843 will continue to get the existing
  `ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP` fixup

### Dependencies:
- `ALC287_FIXUP_TAS2781_I2C` must exist (added Aug 2023)
- `__snd_hda_apply_fixup()` must exist (established infrastructure)
- The pattern is well-tested from `alc298_fixup_lenovo_c940_duet7()`

### Concerns:
- For older stable trees (6.1.y, 5.15.y), the `ALC287_FIXUP_TAS2781_I2C`
  fixup may not exist, so this patch would only be applicable to 6.6+
  stable trees
- Tested by the author on 6.18.5 in both HDA and SOF modes

This is a textbook example of a hardware quirk/workaround that falls
under the "quirks and workarounds" exception for stable. It fixes broken
hardware using a proven pattern with minimal risk to other systems.

**YES**

 sound/hda/codecs/realtek/alc269.c | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c
index 57bad9884158c..32cba2c81ccdd 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -3674,6 +3674,7 @@ enum {
 	ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
 	ALC287_FIXUP_YOGA7_14ITL_SPEAKERS,
 	ALC298_FIXUP_LENOVO_C940_DUET7,
+	ALC287_FIXUP_LENOVO_YOGA_BOOK_9I,
 	ALC287_FIXUP_13S_GEN2_SPEAKERS,
 	ALC256_FIXUP_SET_COEF_DEFAULTS,
 	ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE,
@@ -3757,6 +3758,23 @@ static void alc298_fixup_lenovo_c940_duet7(struct hda_codec *codec,
 	__snd_hda_apply_fixup(codec, id, action, 0);
 }
 
+/* A special fixup for Lenovo Yoga 9i and Yoga Book 9i 13IRU8
+ * both have the very same PCI SSID and vendor ID, so we need
+ * to apply different fixups depending on the subsystem ID
+ */
+static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec,
+					   const struct hda_fixup *fix,
+					   int action)
+{
+	int id;
+
+	if (codec->core.subsystem_id == 0x17aa3881)
+		id = ALC287_FIXUP_TAS2781_I2C; /* Yoga Book 9i 13IRU8 */
+	else
+		id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP; /* Yoga 9i */
+	__snd_hda_apply_fixup(codec, id, action, 0);
+}
+
 static const struct hda_fixup alc269_fixups[] = {
 	[ALC269_FIXUP_GPIO2] = {
 		.type = HDA_FIXUP_FUNC,
@@ -5764,6 +5782,10 @@ static const struct hda_fixup alc269_fixups[] = {
 		.type = HDA_FIXUP_FUNC,
 		.v.func = alc298_fixup_lenovo_c940_duet7,
 	},
+	[ALC287_FIXUP_LENOVO_YOGA_BOOK_9I] = {
+		.type = HDA_FIXUP_FUNC,
+		.v.func = alc287_fixup_lenovo_yoga_book_9i,
+	},
 	[ALC287_FIXUP_13S_GEN2_SPEAKERS] = {
 		.type = HDA_FIXUP_VERBS,
 		.v.verbs = (const struct hda_verb[]) {
@@ -7085,7 +7107,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
 	SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF),
 	SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
 	SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS),
-	SND_PCI_QUIRK(0x17aa, 0x3843, "Yoga 9i", ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP),
+	SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I),
 	SND_PCI_QUIRK(0x17aa, 0x3847, "Legion 7 16ACHG6", ALC287_FIXUP_LEGION_16ACHG6),
 	SND_PCI_QUIRK(0x17aa, 0x384a, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
 	SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
-- 
2.51.0