mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/8] ALSA: untruncated card names
@ 2026-09-15 15:59 Luca Rodenhäuser
  2026-09-15 15:59 ` [RFC PATCH 1/8] ALSA: core: keep non-ASCII bytes out of the card id Luca Rodenhäuser
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Luca Rodenhäuser @ 2026-09-15 15:59 UTC (permalink / raw)
  To: perex, tiwai; +Cc: linux-sound, linux-kernel, Luca Rodenhäuser

This is the series behind the design question from last week, shaped by
what both of you said there.

Link: https://lore.kernel.org/linux-sound/AMBP191MB2886BF4BED02C050FB4D6B76CEBB2@AMBP191MB2886.EURP191.PROD.OUTLOOK.COM/

Takashi:

  "I personally find it OK to have UTF-8 strings in short / long / mixer
   names.  That's no new problem, and it's been over decades.  Of course,
   a broken letter at the end could be a problem and we can fix it in
   either kernel or alsa-lib, too."

Jaroslav:

  "The fields are printed in procfs, so the kernel code should do this."

  "Accepting UTF-8 in fields and provide short-long/long-long name through
   SNDRV_CTL_IOCTL_CARD_BYTES when it does not fit to the standard field
   length is also acceptable (as for the components string)."

So the series does not introduce a second, differently encoded set of name
fields, which is what my first sketch proposed.  It treats the existing
fields as what they are - UTF-8, fixed size - and fixes the two things that
follow from the fixed size.

The problem
-----------

usb_string() converts the UTF-16LE string descriptors of a device into
UTF-8, so dev->product reaches the card name fields as UTF-8 already.
usb_audio_make_shortname() copies it into the 32-byte card->shortname with
strscpy(), which cuts at a byte boundary, so the last sequence can end up
truncated.  What user space receives is then not valid UTF-8 and cannot be
repaired, because the missing bytes never left the kernel.

And cutting cleanly is still cutting: 32 bytes is eight CJK characters.

The series
----------

Two pre-existing fixes first, both in code this series then builds on:

1/8 stops a non-ASCII byte from reaching card->id.  copy_valid_id_string()
    has a path that copies the source byte guarded only by isalpha(), and
    the ctype table classifies the Latin-1 high bytes as letters, so a USB
    product name like "\xc3\x84" "3000" yields an id starting with 0xc3.
    That id becomes a /proc/asound directory name and the hw:CARD= handle,
    and sysfs id_store() rejects exactly what the kernel produces here.

2/8 moves the copy_to_user() in snd_ctl_card_bytes() out of
    snd_ioctl_rwsem.  A user buffer that faults can be made to take
    arbitrarily long, and that lock is global and taken for writing from
    the card probe path.

Then the truncation, which is Takashi's point and, per Jaroslav, belongs in
the kernel because the kernel is what prints these names into procfs:

3/8 adds snd_utf8_strscpy(), which is strscpy() except that it ends the
    copy after the last complete sequence that fits, and uses it in
    usb-audio.  For pure ASCII the output is byte for byte what strscpy()
    produced.  If the string turns out not to be UTF-8 after all, the plain
    cut stands.

    This one stands alone: it is worth applying whether or not the rest of
    the series goes anywhere, and nothing later depends on it.

Then the length, in the shape Jaroslav described:

4/8 adds card->full_shortname and friends, holding the whole name,
    allocated on demand, NULL when the name fit - exactly as
    card->components holds the whole components string of which
    snd_ctl_card_info::components is a cut copy.  "Set" therefore means
    "the fixed field is not the whole story": one name per card, in two
    lengths, never a pair that could disagree.

5/8 returns them as three more SNDRV_CTL_IOCTL_CARD_BYTES types, next to
    the existing _COMPONENTS.  No existing struct or ioctl changes; an old
    kernel answers -EINVAL for an unknown type, which means the same as the
    empty string: the fixed field already holds the whole name.

6/8 adds KUnit coverage.

7/8 makes usb-audio offer them.

8/8 documents it, including what the kernel does not promise.

What stays out of the fixed fields
----------------------------------

Takashi asked what the fixed short/long/mixer names should hold once the
untruncated ones exist - embedded ids like "USB 1234:abcd", or the vendor
and product names when they happen to be ASCII, falling back otherwise.

This series does neither: those fields keep exactly what they hold today,
minus the mid-character cut.  A card that shows up as "Caf\xc3\xa9 Audio"
today is displayed correctly by anything that treats the bytes as UTF-8,
and replacing that with a hex id takes a readable name away.  Falling back
only for non-ASCII names does the same to exactly the devices this is
about, and makes the content depend on a property of the string that user
space cannot predict.  Keeping the fixed fields literally unchanged is also
what makes the untruncated ones safe to add: no existing reader can
regress, because no existing reader sees anything different.

On the id, Jaroslav noted that this is really the only field where
normalisation is needed, since usb-audio hardcodes driver and mixername:

  driver_name: USB-Audio
  mixer_name: USB Mixer

1/8 keeps the id valid, but the result for a non-ASCII name is a fairly
meaningless "D3000".  Deriving it from the USB id instead, as suggested,
would read better, but it changes existing card ids, which udev rules and
hw:CARD= entries point at.  That deserves its own patch and its own
discussion, so it is not in this series; I will send it separately unless
you would rather have it here.

What the kernel promises
------------------------

Two things: a name is well-formed UTF-8 and is never cut inside a sequence,
and it carries no code point that would break a line (C0 and C1 controls,
U+2028, U+2029).  The second one is not cosmetic, since /proc/asound/cards
is line based.

Nothing else.  Invisible and bidirectional format characters, the tag
block, variation selectors and non-characters all pass through, and the
header says so and tells the reader to isolate names for display and to
filter Default_Ignorable_Code_Point itself.

An earlier draft did try to filter those, with a list of ranges.  An
outside review took it apart: U+061C, a real Bidi_Control, fell through, as
did U+2065 and U+206A-206F in the middle of a range the code claimed to
cover, the whole tag block, the variation selectors and U+00AD.  Completing
the list means carrying Unicode character properties in the sound core and
revisiting them every Unicode release, which is the same dependency avoided
by leaving NFC to drivers.  A partial list is worse than none, because the
promise in the header invites user space to skip its own filtering.

The same review found the more basic problem: rejecting a name achieved
nothing, because the fixed field still holds it and readers are told to use
that.  So names are repaired instead - a malformed sequence becomes U+FFFD,
one per bad byte as recommended for a maximal subpart in Unicode TR#36, and
a line breaking code point becomes a space.  The setters cannot fail except
with -ENOMEM.

Still open
----------

1. Limits.  Each untruncated field has a byte cap of four times the
   matching fixed field (128/320/320), so the same number of characters
   always fits.  For card->components the request was to drop the
   artificial cap and let the ioctl report the length.  The same argument
   applies here; say the word and the caps go.

2. full_mixername has no in-tree user, since usb-audio hardcodes
   "USB Mixer".  It is there because the three name fields are one set, but
   if "no user, no API" applies it can be dropped and added with its first
   caller.

3. Should the fixed fields carry the '>' truncation marker that components
   uses, to tell a reader that more is available?  It would be consistent,
   but it changes what every existing reader sees, so I have not done it.

4. Consumers.  alsa-lib and PipeWire have not been asked yet.  If you would
   rather see their interest first, I will do that before a v2.

Testing
-------

18/18 cases of the sound-core-test KUnit suite pass under ARCH=um, also
with CONFIG_KASAN=y.  Two of the new cases are regression tests for the
review findings: one pins that card->id stays ASCII (it fails without 1/8,
with card->id[0] = 0xc3), one pins that the characters the kernel does not
filter really do pass through unchanged, so that nobody later mistakes that
for an oversight.

The sanitiser was fuzzed for 400 000 iterations under ASan and UBSan
against an independent UTF-8 decoder, with the real limits and with limits
small enough that a replacement character cannot fit: no memory errors, no
malformed output, no output over the limit.  The harness includes the
function verbatim from init.c; it can be posted if useful.

sound/ builds warning-free with W=1 on an x86_64 defconfig plus SND,
SND_USB_AUDIO and SND_CORE_TEST.  Each patch builds on its own.
checkpatch --strict is clean apart from warnings about deliberately split
string literals in the test data, where a hex escape is followed by a hex
digit.

The usb-audio patches are tested by build and by inspection only; I have no
device with a non-ASCII product string, so that is the piece that would
benefit from a real test.

Tooling
-------

Per Documentation/process/generated-content.rst: the implementation was
written with the help of an LLM coding assistant, from a prompt describing
the problem and the constraint to fit the existing kernel structure.  The
first draft was then reviewed by a second, independent assistant run that
had not written it, which produced the findings described above; the KUnit
and fuzzing work backing them is that review's, and the series was reworked
in response, and reshaped again after your replies.  Every patch carries an
Assisted-by tag.  I have reviewed the result and stand behind it.

Luca Rodenhäuser (8):
  ALSA: core: keep non-ASCII bytes out of the card id
  ALSA: control: copy the card bytes outside snd_ioctl_rwsem
  ALSA: usb-audio: don't cut the card name inside a UTF-8 character
  ALSA: core: carry the untruncated form of the card names
  ALSA: control: return the untruncated card names via CARD_BYTES
  ALSA: core: add KUnit coverage for the card names
  ALSA: usb-audio: offer the untruncated card names
  ALSA: Documentation: describe the card name fields

 Documentation/sound/designs/card-names.rst    | 178 ++++++++++
 Documentation/sound/designs/index.rst         |   1 +
 .../kernel-api/writing-an-alsa-driver.rst     |  12 +
 include/sound/core.h                          |  28 ++
 include/uapi/sound/asound.h                   |  30 +-
 sound/core/control.c                          |  68 +++-
 sound/core/init.c                             | 333 ++++++++++++++++-
 sound/core/sound_kunit.c                      | 334 ++++++++++++++++++
 sound/usb/card.c                              | 106 ++++--
 9 files changed, 1045 insertions(+), 45 deletions(-)
 create mode 100644 Documentation/sound/designs/card-names.rst

-- 
2.43.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-09-15 15:59 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 15:59 [RFC PATCH 0/8] ALSA: untruncated card names Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 1/8] ALSA: core: keep non-ASCII bytes out of the card id Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 2/8] ALSA: control: copy the card bytes outside snd_ioctl_rwsem Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 3/8] ALSA: usb-audio: don't cut the card name inside a UTF-8 character Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 4/8] ALSA: core: carry the untruncated form of the card names Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 5/8] ALSA: control: return the untruncated card names via CARD_BYTES Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 6/8] ALSA: core: add KUnit coverage for the card names Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 7/8] ALSA: usb-audio: offer the untruncated " Luca Rodenhäuser
2026-09-15 15:59 ` [RFC PATCH 8/8] ALSA: Documentation: describe the card name fields Luca Rodenhäuser

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®