CVE Candidate: Kernel Divide-by-Zero in dac33_calculate_times via UTHR_FROM_PERIOD_SIZE

File: sound/soc/codecs/tlv320dac33.c
Severity: High (Kernel Panic / Local DoS)
Type: Divide-by-Zero (CWE-369)


Vulnerability Description

The macro UTHR_FROM_PERIOD_SIZE defined at line 47 contains an unconditional division whose denominator can reach zero under reachable conditions:

#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \
    (((samples)*5000) / (((burstrate)*5000) / ((burstrate) - (playrate))))

The inner sub-expression (burstrate) - (playrate) is unsigned integer subtraction. When burstrate == playrate, this evaluates to zero, causing a kernel divide-by-zero exception (#DE fault on x86) — a hard kernel oops / panic.


Trigger Path

The macro is invoked at dac33_calculate_times(), line 1099:

dac33->uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate,
                                    dac33->burst_rate) + 9;

dac33->burst_rate is computed in dac33_hw_params() via:

#define CALC_BURST_RATE(bclkdiv, bclk_per_sample) \
    (BURST_BASEFREQ_HZ / bclkdiv / bclk_per_sample)
// BURST_BASEFREQ_HZ = 49152000

Concrete crash scenario

Parameter Value
Format SNDRV_PCM_FORMAT_S32_LE
bclk_per_sample 64
burst_bclkdiv 16 (valid u8, set via platform data)
Computed burst_rate 49152000 / 16 / 64 = 48000
Playback rate 48000 Hz

With these values: burst_rate - playrate = 48000 - 48000 = 0.

The expression (burstrate)*5000 / 0 executes in kernel context, producing a divide-by-zero trap.

For 16-bit format (bclk_per_sample = 32), the crash triggers at burst_bclkdiv = 32 with a 48000 Hz stream.


Control Flow to Reach the Bug

User opens PCM device (ALSA)
  → sets FIFO Mode to Mode7 via kcontrol "FIFO Mode"  (dac33_set_fifo_mode)
  → calls hw_params with rate=48000, format=S32_LE     (dac33_hw_params)
  → starts stream (SNDRV_PCM_TRIGGER_START)            (dac33_pcm_trigger)
     → schedules dac33_work
        → dac33_prefill_handler
           → calls dac33_playback_event (SND_SOC_DAPM_PRE_PMU)
              → dac33_calculate_times()
                 → UTHR_FROM_PERIOD_SIZE(period_size, 48000, 48000)
                    → (burst_rate - playrate) == 0  →  DIVIDE BY ZERO  ← CRASH

Any local user with access to the audio device (/dev/snd/pcmC0D0p or similar) can trigger this.


Secondary Issue: Integer Overflow in Same Macro

Even when burstrate != playrate, the expression (samples) * 5000 operates on unsigned int period_size (line 1064 stores snd_pcm_uframes_t — an unsigned long — into an unsigned int, truncating on 64-bit). When a userspace process sets period_size > 858,993 samples, period_size * 5000 silently overflows 32-bit unsigned, producing a wrapped-around (smaller) uthr value and corrupting FIFO threshold configuration. The subsequent clamping at lines 1101–1104 partially mitigates memory safety impact but does not prevent the wrong FIFO threshold being programmed into hardware.


Root Cause

No guard against burst_rate == playrate before the division:

// Missing check:
if (dac33->burst_rate <= rate) {
    dev_err(...);
    return -EINVAL;
}

Impact

  • Kernel panic (oops): exploitable by any local user with access to the ALSA audio device, achieving an unprivileged local denial-of-service.
  • On systems where the panic handler reboots the machine, this is a reliable reboot primitive.
  • If ALSA device nodes are accessible to an untrusted container or VM guest, this may cross privilege boundaries.

Suggested Fix

Add a pre-check in dac33_calculate_times() before invoking the macro:

case DAC33_FIFO_MODE7:
    if (dac33->burst_rate <= rate) {
        dev_err(component->dev,
            "burst_rate (%u) must exceed playback rate (%u)\n",
            dac33->burst_rate, rate);
        return;
    }
    dac33->uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate,
                                        dac33->burst_rate) + 9;

Additionally, fix the snd_pcm_uframes_tunsigned int truncation:

- unsigned int period_size = substream->runtime->period_size;
+ snd_pcm_uframes_t period_size = substream->runtime->period_size;