mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: 徐晗翔 <xuhx@hust.edu.cn>
To: perex@perex.cz, tiwai@suse.com
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: ALSA: ctxfi: do not free a running system timer when timer_delete_sync_try() returns -1 (UAF)
Date: Fri, 13 Nov 2026 20:03:25 +0800 (GMT+08:00)	[thread overview]
Message-ID: <6776b38b.262de.1a1d4ca8711.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>

[-- Attachment #1: Type: text/plain, Size: 4108 bytes --]

`ct_systimer_prepare()` is used as both the prepare callback and the
`free_instance` callback.  It calls `timer_delete_sync_try()` and ignores the
return value; `-1` means the timer callback is running on another CPU and the
timer was NOT deleted.  `ct_timer_instance_free()` then removes the instance
and `kfree()`s it while `ct_systimer_callback()` can still dereference
`ti->substream`, `ti->substream->runtime`, `ti->apcm`, and `ti->position`.
A normal PCM close with `use_system_timer=1` can therefore produce a UAF of
the timer instance, the PCM runtime, and the driver's private PCM object.

The detailed description:

* Test environment / affected version

  Linux 7.3.0, source analysis plus a QEMU/KASAN primitive reproducer.
  The reproducer uses the real exported `timer_delete_sync_try()` and a timer
  callback; it does not call the static ctxfi functions or emulate ctxfi
  hardware.

* Mechanism

  `ct_systimer_callback()` (`sound/pci/ctxfi/cttimer.c:63-87`) reads
  `ti->substream`, `substream->runtime`, `ti->apcm`,
  `substream->ops->pointer`, `apcm->interrupt`, and `ti->position`; it takes
  `ti->lock` only near the end and may re-arm the timer.

  `ct_systimer_stop()` (`cttimer.c:105-110`) locks `ti->lock`, clears
  `ti->running`, and calls `timer_delete()`.  `ct_systimer_prepare()`
  (`cttimer.c:112-116`) then does:

    ct_systimer_stop(ti);
    timer_delete_sync_try(&ti->timer);

  `#define ct_systimer_free ct_systimer_prepare` at line 118 makes the same
  function the `free_instance` callback, and `ct_systimer_ops.free_instance`
  is set at 120-126.  `ct_timer_instance_free()` (`cttimer.c:360-373`) calls
  `atimer->ops->stop()`, then `free_instance()`, then `list_del()` under
  `list_lock`, then `kfree(ti)`.

  `timer_delete_sync_try()` (`kernel/time/timer.c:1488-1492`) returns 0 if the
  timer was not pending, 1 if it was pending and deactivated, and -1 if the
  callback is running on another CPU.  It never waits.  The return value is
  ignored here.

  The PCM close path is `snd_pcm_release_substream()`
  (`sound/core/pcm_native.c:2785-2805`) -> `snd_pcm_drop()` ->
  `snd_pcm_detach_substream()` (`sound/core/pcm.c:978-1005`).  The ctxfi
  `runtime->private_free` is `ct_atc_pcm_free_substream()`
  (`sound/pci/ctxfi/ctpcm.c:103-112`), which calls
  `ct_timer_instance_free(apcm->timer)` and then `kfree(apcm)`.  Thus a
  callback running on another CPU can use `ti`, `ti->substream->runtime`, and
  `apcm` after they are freed.

* Why the fix cannot simply sleep in ct_systimer_prepare()

  The same callback is also `ct_timer_ops.prepare`, called from
  `ct_timer_prepare()` while `snd_pcm_prepare()` holds
  `pcm_stream_lock_irq` (`pcm_native.c:2042-2050`).  Replacing the try with a
  sleeping `timer_delete_sync()` there would cause "scheduling while atomic".
  The prepare path must keep a non-sleeping delete/try; the close/free path
  needs a separate method.

* Trigger model

  `use_system_timer=1` (module parameter; default is false), open/prepare a
  PCM, let the system timer callback run, then close the PCM while the
  callback is still active on another CPU.  SMP/preemption required.
  Consequence: UAF in timer softirq; crash or memory corruption.

* Dynamic evidence

  `sndpci_race.c`, mode `v3`, runs the real
  `timer_delete_sync_try()`; when it returns -1 the test frees the object while
  the callback is in `mdelay()`. Show 16 KASAN reports:

    sndpci_race: v3 iter=0 delete_sync_try=-1
    BUG: KASAN: slab-use-after-free in v3_callback
    ...
    Allocated by task 86: v3_thread
    Freed by task 86: kfree -> v3_thread



* Suggested direction

  Split the callbacks: keep `ct_systimer_prepare()` non-sleeping for the
  prepare path, and add a separate `ct_systimer_free()` for close/free that
  first clears the callback/running state and then calls
  `timer_shutdown_sync(&ti->timer)` (or `timer_delete_sync()`), before the
  instance list removal and `kfree(ti)`.  Do not call the sleeping helper from
  the PCM prepare path.


Please keep me in Cc; I am not subscribed to the lists.


Regards,
Hanxiang Xu
xuhx@hust.edu.cn

[-- Attachment #2: sndpci_race.c --]
[-- Type: text/plain, Size: 9729 bytes --]

// SPDX-License-Identifier: GPL-2.0
/*
 * sndpci_race.c - minimal ALSA/kernel lifetime-race reproducers for the
 * sound/pci manual-review cases V3/V4/V5.
 *
 * The module deliberately reproduces the *lifetime patterns* used by
 * ctxfi/asihpi, using only exported ALSA core and timer APIs.  It is not a
 * ctxfi/asihpi emulator: those drivers need hardware that this QEMU does not
 * provide.  The purpose is to test whether the core/API windows identified in
 * the report are real on an instrumented KASAN kernel.
 *
 * modes:
 *   v3  - timer_delete_sync_try() returns -1 while the callback runs on
 *         another CPU; free the object anyway (ctxfi system-timer pattern).
 *   pcm - create a fake ALSA card with two playback substreams.  The guest
 *         program pcmtest then exercises close (V4) or link+close (V5).
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/cpumask.h>
#include <linux/sched.h>
#include <linux/container_of.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>

#define DRV "sndpci_race"
#define COOKIE 0x5a5a1234deadbeefULL

static char *mode = "pcm";
module_param(mode, charp, 0444);
static bool safe;
module_param(safe, bool, 0444);
MODULE_PARM_DESC(mode, "v3 or pcm");

/* ------------------------------------------------------------------ */
/* V3: timer_delete_sync_try() ignored -1                              */
/* ------------------------------------------------------------------ */
struct v3_obj {
	struct timer_list timer;
	u64 cookie;
	struct task_struct *thread;
};

static void v3_callback(struct timer_list *t)
{
	struct v3_obj *v = container_of(t, struct v3_obj, timer);

	/* Keep the callback alive long enough for the freeing CPU to run. */
	mdelay(20);
	pr_info(DRV ": v3 callback cookie=%#llx\n", v->cookie);
	/* Second read after the delay: the object may already be freed. */
	if (v->cookie == COOKIE)
		pr_info(DRV ": v3 callback read-after-free survived\n");
}

static int v3_thread(void *arg)
{
	struct v3_obj *v;
	int i, ret;

	for (i = 0; i < 8; i++) {
		v = kzalloc(sizeof(*v), GFP_KERNEL);
		if (!v)
			return -ENOMEM;
		v->cookie = COOKIE;
		timer_setup(&v->timer, v3_callback, 0);
		/* Pin the callback to CPU0; this thread runs on CPU1. */
		add_timer_on(&v->timer, 0);
		usleep_range(2000, 3000);
		ret = timer_delete_sync_try(&v->timer);
		pr_info(DRV ": v3 iter=%d delete_sync_try=%d\n", i, ret);
		if (ret == -1) {
			/* BUG: callback is running on CPU0; free it anyway. */
			if (safe)
				timer_delete_sync(&v->timer);
			kfree(v);
		} else {
			timer_delete_sync(&v->timer);
			kfree(v);
		}
		usleep_range(60000, 70000);
	}
	pr_info(DRV ": v3 done\n");
	return 0;
}

/* ------------------------------------------------------------------ */
/* V4/V5: fake ALSA PCM with a driver-private dpcm and a timer        */
/* ------------------------------------------------------------------ */
struct race_pcm {
	struct timer_list timer;
	struct snd_pcm_substream *substream;
	u64 cookie;
	bool respawn;
	bool active;
};

static struct snd_card *race_card;
static struct platform_device *race_pdev;
static struct snd_pcm *race_pcm_dev;

static void race_pcm_timer(struct timer_list *t)
{
	struct race_pcm *dpcm = container_of(t, struct race_pcm, timer);
	struct snd_pcm_substream *s;
	struct snd_pcm_runtime *victim_rt = NULL;
	u64 cookie;

	if (!dpcm->active)
		return;
	pr_info(DRV ": timer start dpcm=%px\n", dpcm);

	/*
	 * V5 pattern: walk the link group without the group lock and read
	 * each member's runtime/private_data.  V4 uses the same callback for
	 * the current stream; the delay widens the close/free window.
	 */
	cookie = dpcm->cookie;
	if (cookie == COOKIE && dpcm->substream && dpcm->substream->runtime) {
		snd_pcm_group_for_each_entry(s, dpcm->substream) {
			struct snd_pcm_runtime *rt = READ_ONCE(s->runtime);
			struct race_pcm *d = NULL;

			if (rt)
				d = READ_ONCE(rt->private_data);
			if (d && d->cookie == COOKIE) {
				/* Force the compiler to keep the load. */
				barrier();
			}
			if (rt) {
				(void)READ_ONCE(rt->dma_area);
				if (s != dpcm->substream && !victim_rt)
					victim_rt = rt;
			}
			/* A real driver also reports elapsed periods. */
			if (rt)
				snd_pcm_period_elapsed(s);
		}
	}

	/* Widen the window between the callback's load and the free. */
	mdelay(20);
	if (victim_rt) {
		struct race_pcm *d = READ_ONCE(victim_rt->private_data);

		pr_info(DRV ": victim use rt=%px d=%px\n", victim_rt, d);
		(void)READ_ONCE(victim_rt->dma_area);
	}
	pr_info(DRV ": timer end dpcm=%px\n", dpcm);
	if (dpcm->respawn) {
		if (!timer_pending(&dpcm->timer))
			add_timer_on(&dpcm->timer, 0);
		else
			mod_timer(&dpcm->timer, jiffies + 1);
	}
}

static void race_pcm_free(struct snd_pcm_runtime *runtime)
{
	struct race_pcm *dpcm = runtime->private_data;

	pr_info(DRV ": pcm private_free dpcm=%px\n", dpcm);
	if (safe)
		timer_shutdown_sync(&dpcm->timer);
	kfree(dpcm);
}

static int race_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct race_pcm *dpcm;

	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
	if (!dpcm)
		return -ENOMEM;
	dpcm->substream = substream;
	dpcm->cookie = COOKIE;
	timer_setup(&dpcm->timer, race_pcm_timer, 0);
	runtime->private_data = dpcm;
	runtime->private_free = race_pcm_free;
	runtime->hw.info = SNDRV_PCM_INFO_MMAP |
			   SNDRV_PCM_INFO_INTERLEAVED |
			   SNDRV_PCM_INFO_MMAP_VALID;
	runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
	runtime->hw.rates = SNDRV_PCM_RATE_44100;
	runtime->hw.rate_min = 44100;
	runtime->hw.rate_max = 44100;
	runtime->hw.channels_min = 2;
	runtime->hw.channels_max = 2;
	runtime->hw.buffer_bytes_max = 64 * 1024;
	runtime->hw.period_bytes_min = 1024;
	runtime->hw.period_bytes_max = 4096;
	runtime->hw.periods_min = 2;
	runtime->hw.periods_max = 8;
	pr_info(DRV ": pcm open sub=%px dpcm=%px\n", substream, dpcm);
	return 0;
}

static int race_pcm_close(struct snd_pcm_substream *substream)
{
	pr_info(DRV ": pcm .close sub=%px\n", substream);
	return 0;
}

static int race_pcm_ioctl(struct snd_pcm_substream *substream,
			  unsigned int cmd, void *arg)
{
	pr_info(DRV ": pcm ioctl cmd=%u\n", cmd);
	return snd_pcm_lib_ioctl(substream, cmd, arg);
}

static int race_pcm_hw_params(struct snd_pcm_substream *substream,
			      struct snd_pcm_hw_params *params)
{
	pr_info(DRV ": hw_params rate=%u ch=%u fmt=%u period_bytes=%u buffer_bytes=%u\n",
		params_rate(params), params_channels(params), params_format(params),
		params_period_bytes(params), params_buffer_bytes(params));
	return 0;
}

static int race_pcm_prepare(struct snd_pcm_substream *substream)
{
	return 0;
}

static int race_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct race_pcm *dpcm = substream->runtime->private_data;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
		if (substream != race_pcm_dev->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
			break;
		dpcm->active = true;
		dpcm->respawn = true;
		if (!timer_pending(&dpcm->timer))
			add_timer_on(&dpcm->timer, 0);
		else
			mod_timer(&dpcm->timer, jiffies + 1);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		dpcm->active = false;
		dpcm->respawn = false;
		/* Deliberately not timer_delete_sync(): mirrors asihpi. */
		timer_delete(&dpcm->timer);
		break;
	}
	return 0;
}

static snd_pcm_uframes_t race_pcm_pointer(struct snd_pcm_substream *substream)
{
	return 0;
}

static const struct snd_pcm_ops race_pcm_ops = {
	.open = race_pcm_open,
	.close = race_pcm_close,
	.ioctl = race_pcm_ioctl,
	.hw_params = race_pcm_hw_params,
	.prepare = race_pcm_prepare,
	.trigger = race_pcm_trigger,
	.pointer = race_pcm_pointer,
};

static int __init sndpci_init(void)
{
	struct task_struct *t;
	int ret;

	pr_info(DRV ": init mode=%s\n", mode);

	if (!strcmp(mode, "v3")) {
		t = kthread_create(v3_thread, NULL, "sndpci_v3");
		if (IS_ERR(t))
			return PTR_ERR(t);
		kthread_bind(t, 1);
		wake_up_process(t);
		return 0;
	}

	race_pdev = platform_device_register_simple("sndpci_race", -1, NULL, 0);
	if (IS_ERR(race_pdev))
		return PTR_ERR(race_pdev);
	ret = snd_card_new(&race_pdev->dev, 0, "SndPciRace", THIS_MODULE, 0, &race_card);
	if (ret < 0) {
		pr_err(DRV ": snd_card_new failed: %d\n", ret);
		return ret;
	}
	ret = snd_pcm_new(race_card, "SndPciRace", 0, 0, 2, &race_pcm_dev);
	if (ret < 0) {
		pr_err(DRV ": snd_pcm_new failed: %d\n", ret);
		snd_card_free(race_card);
		return ret;
	}
	snd_pcm_set_ops(race_pcm_dev, SNDRV_PCM_STREAM_CAPTURE,
			&race_pcm_ops);
	snd_pcm_lib_preallocate_pages_for_all(race_pcm_dev, SNDRV_DMA_TYPE_CONTINUOUS,
						      NULL, 64 * 1024, 64 * 1024);
	ret = 0;
	pr_info(DRV ": managed_buffer ret=%d dma_type=%d area=%px bytes=%zu\n", ret,
		race_pcm_dev->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer.dev.type,
		race_pcm_dev->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer.area,
		race_pcm_dev->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer.bytes);
	ret = snd_card_register(race_card);
	if (ret < 0) {
		pr_err(DRV ": snd_card_register failed: %d\n", ret);
		snd_card_free(race_card);
		return ret;
	}
	pr_info(DRV ": card registered\n");
	return 0;
}

static void __exit sndpci_exit(void)
{
	pr_info(DRV ": exit\n");
	if (race_card)
		snd_card_free(race_card);
	if (race_pdev)
		platform_device_unregister(race_pdev);
}

module_init(sndpci_init);
module_exit(sndpci_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sound/pci lifetime-race reproducer (V3/V4/V5)");

  parent reply	other threads:[~2026-11-13 12:03 UTC|newest]

Thread overview: 47+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 12:35 usb: devio: usbdev_release() has no barrier against an in-flight async_completed() callback (use-after-free window on struct usb_dev_state)gregkh@linuxfoundation.org 徐晗翔
2026-10-27 14:12 ` Greg KH
2026-11-03 11:21   ` mm/slub: has_pcs_used() reads remote per-CPU sheaf state from flush_all_cpus_locked() without synchronization (data race; KCSAN blind spot: slub.o is uninstrumented) 徐晗翔
2026-11-04  9:41     ` Qing Wang
2026-11-04 10:07       ` Qing Wang
2026-11-03 11:30   ` futex/hrtimer: hrtimer_sleeper::task is read unsynchronized in 16 places on v7.3 -- the accessor series that fixes this is not in 7.3; please make sure it reaches the affected trees (backport nudge + stock-kernel KCSAN evidence) 徐晗翔
2026-11-03 11:48   ` cgroup: fork vs cgroup.procs write race in favordynmods mode -- v7.3 is affected; please make sure bf6fbf46b5ff reaches the stable trees (repro incl. uninstrumented natural hit) 徐晗翔
2026-11-03 14:51     ` Sasha Levin
2026-11-03 11:57   ` net: rtnetlink: rtnl_link_unregister() vs in-flight RTM_GETLINK dump readers -- v7.3 lacks the synchronize_rcu() fix (Fixes: 6b57ff21a310 family); A/B reproduction on stock 7.3 徐晗翔
2026-11-03 15:49     ` Eric Dumazet
2026-11-04 15:03     ` Sasha Levin
2026-11-03 12:07   ` wifi: wilc1000: SDIO/SPI remove destroys the srcu_struct before wiphy_unregister() -- late cfg80211 callbacks write through dangling srcu state (unfixed upstream; controlled + natural Oops) 徐晗翔
2026-11-03 12:10   ` gpio: gpiolib: final put of the gpio_device happens inside the SRCU read critical section on the setup-failure path -- deterministic use-after-free read at guard release (unfixed upstream) 徐晗翔
2026-11-05  8:52     ` Linus Walleij
2026-11-04 12:54   ` soc: qcom: pmic_glink_altmode: fix use-after-free on unbind (no cancel_work_sync for port work and enable_work) 徐晗翔
2026-11-04 13:04     ` Greg KH
2026-11-04 12:57   ` usb: typec: anx7411: unregister partner/altmodes before stopping the IRQ work -- use-after-free, double unregister, orphan partner on remove 徐晗翔
2026-11-04 13:28     ` Greg KH
2026-11-04 12:58   ` usb: typec: ucsi: deadlock on cros_ec unbind -- connector lock held across cancel_delayed_work_sync vs the mode-selection work's unbounded trylock polling 徐晗翔
2026-11-04 13:00   ` usb: typec: stusb160x/rt1719/hd3ss3220: IRQ thread vs remove -- double unregister / use-after-free on the partner and port 徐晗翔
2026-11-04 13:01   ` usb: typec: ucsi: displayport: data race on dp->header/ vdo_data/vdo_size -- work reads them unlocked while writers hold con->lock (VDM mismatch) 徐晗翔
2026-11-04 13:02   ` usb: typec: class: three lifetime/locking gaps around the mux/switch handles and mode_selection (drvdata UAF after provider unbind; no concurrent protocol for set/put; unlocked state_update vs delete) 徐晗翔
2026-11-10 12:56   ` driver core: uevent: netns teardown frees uevent_sock while a tagged uevent is still being broadcast (UAF) 徐晗翔
2026-11-10 13:00   ` 徐晗翔
2026-11-10 18:48     ` Greg KH
2026-11-10 13:05   ` ACPI: CPPC: per-CPU cpc_desc is freed on processor unbind without clearing the published pointer, causing UAF in cpufreq accessors 徐晗翔
2026-11-10 13:07   ` ACPI: CPPC: shared PCC subspace data can be freed while another CPU probes, reads, or tears it down 徐晗翔
2026-11-10 14:47     ` Christian Loehle
2026-11-10 13:11   ` ACPI: ACPICA: Reset can delete an event semaphore while another thread is blocked in Wait 徐晗翔
2026-11-10 13:14   ` power: supply: bq25890: pump-express work can call power_supply_changed after the psy is unregistered 徐晗翔
2026-11-10 13:17   ` power: supply: core: get/set property callbacks can run after unregister and read freed driver data 徐晗翔
2026-11-10 13:18   ` power: supply: core: system-battery enumeration can return an unregistered battery 徐晗翔
2026-11-10 13:21   ` power: supply: core: changed work can run after unregister and use freed LED triggers 徐晗翔
2026-11-10 13:23   ` power: supply: bq24190: IRQ handler can call power_supply_changed after the psy is unregistered 徐晗翔
2026-11-10 13:34   ` 徐晗翔
2026-11-11  7:17   ` genirq: NMI dispatch reads struct irqaction with no quiescence -- use-after-free across free_nmi()'s kfree (x86 verification on the arch-independent core path) 徐晗翔
2026-11-11  7:26   ` genirq: NMI API lacks the request_mutex / dev_id matching / idempotency semantics that free_irq() has -- double free_nmi releases chip PM and module references twice (verified) 徐晗翔
2026-11-11  7:30   ` kernfs: kernfs_drain()'s fast-path decision reads the active count without acquire ordering -- on weakly-ordered architectures the forced ->release()/unmap can be skipped 徐晗翔
2026-11-11 13:41     ` Greg KH
2026-11-13 11:58   ` ALSA: ctxfi: free atimer only after the timer IRQ is quiesced 徐晗翔
2026-11-13 12:03   ` 徐晗翔 [this message]
2026-11-13 12:04   ` ALSA: asihpi: free IRQ before snd_card_free()/HPI delete on HPI6205 removal (UAF) 徐晗翔
2026-11-13 12:06   ` ALSA: asihpi: quiesce PCM timer and IRQ before freeing dpcm on close (UAF) 徐晗翔
2026-11-13 12:15   ` ALSA: pcm: link-group walk in timer callback can use a closed substream's freed runtime (UAF) 徐晗翔
2026-11-13 12:28   ` ALSA: usb-audio: us144mkii: PCM close frees runtime while playback/feedback URB completion and capture work still use it (UAF) 徐晗翔
2026-11-13 12:29   ` ALSA: usb-audio: us144mkii: queued stop_pcm_work calls snd_pcm_stop() after PCM close and dereferences NULL runtime 徐晗翔
2026-11-13 12:33   ` sched/topology: old domain chains are queued for RCU freeing before the per-CPU pointers are updated, so a late reader can still dereference a freed sched_domain and sched_domain_shared (UAF) 徐晗翔

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6776b38b.262de.1a1d4ca8711.Coremail.xuhx@hust.edu.cn \
    --to=xuhx@hust.edu.cn \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=perex@perex.cz \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®