[RFC PATCH 5/8] ALSA: control: return the untruncated card names via CARD_BYTES

From: Luca Rodenhäuser

Date: Tue Sep 15 2026 - 12:13:37 EST


struct snd_ctl_card_info is fixed-size and cannot grow, so the untruncated
names need another way out. SNDRV_CTL_IOCTL_CARD_BYTES already carries one
untruncated card string, with a query-then-read protocol and a __u64 user
pointer that needs no compat handling, so add three types to it rather than
a new ioctl: SND_CTL_CARD_BTYPE_NAME, _LONGNAME and _MIXERNAME, next to the
existing _COMPONENTS. No existing structure or ioctl changes, so old
binaries are untouched.

A name that fits into its fixed field is returned as an empty string rather
than repeated, which tells the reader that snd_ctl_card_info already has
the whole name. An older kernel answers -EINVAL for the unknown type,
which means the same thing, so a reader needs no version check; PVERSION
reports 2.0.11 where the types exist.

The header says what the kernel guarantees about a name - well-formed
UTF-8, nothing that would break a line, never cut inside a sequence - and
at more length what it does not: no NFC, no homoglyph detection, and no
filtering of invisible or bidirectional format characters, with a pointer
at what user space has to do instead. A promise that cannot be kept is
worse than no promise, because it invites readers to skip their own
filtering.

Pull the string lookup out into a helper so all four types share one output
path instead of repeating the copy-out four times.

Assisted-by: LLM
Signed-off-by: Luca Rodenhäuser <otzelot2021@xxxxxxxxxx>
---
include/uapi/sound/asound.h | 30 +++++++++++++++++++++++++++++-
sound/core/control.c | 36 ++++++++++++++++++++++++++++--------
2 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index c11da9656e..bebd1b1049 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -1058,7 +1058,7 @@ struct snd_timer_tread {
* *
****************************************************************************/

-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10)
+#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 11)

struct snd_ctl_card_info {
int card; /* card number */
@@ -1076,11 +1076,39 @@ struct snd_ctl_card_info {
* Card components can exceed the fixed 128 bytes in snd_ctl_card_info.
* Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS
* to retrieve the full string.
+ *
+ * SND_CTL_CARD_BTYPE_NAME, _LONGNAME and _MIXERNAME return the untruncated
+ * form of the name, longname and mixername fields of struct snd_ctl_card_info,
+ * in the same way SND_CTL_CARD_BTYPE_COMPONENTS returns the untruncated
+ * components string. Available since SNDRV_CTL_VERSION 2.0.11.
+ *
+ * A name that fits into its fixed field is not repeated here: an empty result
+ * means the field in struct snd_ctl_card_info already holds the whole name.
+ * An older kernel answers -EINVAL for the unknown type, which means the same.
+ * So there is one name per card, available in two lengths, and never two names
+ * that could disagree.
+ *
+ * Card names are UTF-8. The kernel guarantees that what it returns here is
+ * well-formed UTF-8 carrying no code point that would break a line (C0 and C1
+ * controls, U+2028, U+2029), and that it is never cut inside a multi-byte
+ * sequence. The fixed fields are cut at a character boundary too.
+ *
+ * Nothing beyond that is guaranteed, deliberately. Invisible, bidirectional
+ * and deprecated format characters pass through, NFC is only a recommendation
+ * to drivers, and homoglyphs cannot be detected at all: all of that needs the
+ * Unicode character properties, which belong in user space and not in the
+ * sound core, and a range list in the kernel would go stale with every
+ * Unicode release. So a name is a display string and never a security
+ * boundary: isolate it for display (U+2066/U+2069), filter
+ * Default_Ignorable_Code_Point, and match cards on id, driver or components.
*/

/* Type values for struct snd_ctl_card_bytes::type */
enum {
SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */
+ SND_CTL_CARD_BTYPE_NAME = 2, /* full short name */
+ SND_CTL_CARD_BTYPE_LONGNAME = 3, /* full long name */
+ SND_CTL_CARD_BTYPE_MIXERNAME = 4, /* full mixer name */
};

struct snd_ctl_card_bytes {
diff --git a/sound/core/control.c b/sound/core/control.c
index e404cb55a5..cfe451f0f4 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -14,6 +14,7 @@
#include <linux/mm.h>
#include <linux/math64.h>
#include <linux/sched/signal.h>
+#include <linux/err.h>
#include <linux/cleanup.h>
#include <sound/core.h>
#include <sound/minors.h>
@@ -901,6 +902,26 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
return 0;
}

+/* Pick the card string a SND_CTL_CARD_BTYPE_* value refers to.
+ * Returns NULL for an unset field, which is handed out as an empty string.
+ * Must be called with snd_ioctl_rwsem held.
+ */
+static const char *snd_ctl_card_btype_str(struct snd_card *card, __u32 type)
+{
+ switch (type) {
+ case SND_CTL_CARD_BTYPE_COMPONENTS:
+ return card->components;
+ case SND_CTL_CARD_BTYPE_NAME:
+ return card->full_shortname;
+ case SND_CTL_CARD_BTYPE_LONGNAME:
+ return card->full_longname;
+ case SND_CTL_CARD_BTYPE_MIXERNAME:
+ return card->full_mixername;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
static int snd_ctl_card_bytes(struct snd_card *card,
struct snd_ctl_card_bytes *info,
unsigned int __user *data_len_out)
@@ -909,22 +930,21 @@ static int snd_ctl_card_bytes(struct snd_card *card,
bool too_small = false;
unsigned int data_len;

- if (info->type != SND_CTL_CARD_BTYPE_COMPONENTS)
- return -EINVAL;
-
scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
- const char *components = card->components;
+ const char *str = snd_ctl_card_btype_str(card, info->type);

- if (!components)
- components = "";
+ if (IS_ERR(str))
+ return PTR_ERR(str);
+ if (!str)
+ str = "";

- data_len = strlen(components) + 1;
+ data_len = strlen(str) + 1;

if (info->data && info->data_allocated != 0) {
if (info->data_allocated < data_len) {
too_small = true;
} else {
- copy = kmemdup(components, data_len, GFP_KERNEL);
+ copy = kmemdup(str, data_len, GFP_KERNEL);
if (!copy)
return -ENOMEM;
}
--
2.43.0