diff options
| author | Ali Ahmet Memis <ali@iusegentoo.com> | 2026-08-21 01:45:27 +0000 |
|---|---|---|
| committer | Stafford Horne <shorne@gmail.com> | 2026-08-29 07:32:26 +0100 |
| commit | 78004e9a87f240df03e2f73120d291763c32e0a7 (patch) | |
| tree | 137c22dd795222931b9d2bf2501d37cd766741aa /sound/firewire/fireface/ff-stream.c | |
| download | linux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.tar.gz linux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.zip | |
openrisc: fix arbitrary kernel memory access via or1k_atomic syscallgrafted
sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user
pointers, v1 and v2, and swaps the words they point to in hand-written
assembly.
l.lwz r29,0(r4)
l.lwz r27,0(r5)
l.sw 0(r4),r27
l.sw 0(r5),r29
The pointers are not checked with access_ok(). The four memory
accesses also have no exception table entries.
A caller passes a kernel address as either pointer, and the syscall
reads from and writes to it directly.
This gives an unprivileged process a kernel read/write primitive. It
overwrites kernel data such as the sys_call_table, gaining code
execution in kernel context.
Check both pointers before entering the critical section. Add fixups
for the four memory accesses so faults on valid but unmapped user
addresses return -EFAULT.
[shorne@gmail.com: fix comment style]
Fixes: 9d02a4283e9c ("OpenRISC: Boot code")
Cc: stable@vger.kernel.org
Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
Signed-off-by: Stafford Horne <shorne@gmail.com>
Diffstat (limited to 'sound/firewire/fireface/ff-stream.c')
| -rw-r--r-- | sound/firewire/fireface/ff-stream.c | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000..ba42490f2 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + */ + +#include "ff.h" + +#define READY_TIMEOUT_MS 200 + +int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc, + enum snd_ff_stream_mode *mode) +{ + static const enum snd_ff_stream_mode modes[] = { + [CIP_SFC_32000] = SND_FF_STREAM_MODE_LOW, + [CIP_SFC_44100] = SND_FF_STREAM_MODE_LOW, + [CIP_SFC_48000] = SND_FF_STREAM_MODE_LOW, + [CIP_SFC_88200] = SND_FF_STREAM_MODE_MID, + [CIP_SFC_96000] = SND_FF_STREAM_MODE_MID, + [CIP_SFC_176400] = SND_FF_STREAM_MODE_HIGH, + [CIP_SFC_192000] = SND_FF_STREAM_MODE_HIGH, + }; + + if (sfc >= CIP_SFC_COUNT) + return -EINVAL; + + *mode = modes[sfc]; + + return 0; +} + +static inline void finish_session(struct snd_ff *ff) +{ + ff->spec->protocol->finish_session(ff); + ff->spec->protocol->switch_fetching_mode(ff, false); +} + +static int init_stream(struct snd_ff *ff, struct amdtp_stream *s) +{ + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + int err; + + if (s == &ff->tx_stream) { + resources = &ff->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &ff->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(s, ff->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_ff *ff, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &ff->tx_stream) + fw_iso_resources_destroy(&ff->tx_resources); + else + fw_iso_resources_destroy(&ff->rx_resources); +} + +int snd_ff_stream_init_duplex(struct snd_ff *ff) +{ + int err; + + err = init_stream(ff, &ff->rx_stream); + if (err < 0) + return err; + + err = init_stream(ff, &ff->tx_stream); + if (err < 0) { + destroy_stream(ff, &ff->rx_stream); + return err; + } + + err = amdtp_domain_init(&ff->domain); + if (err < 0) { + destroy_stream(ff, &ff->rx_stream); + destroy_stream(ff, &ff->tx_stream); + } + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + amdtp_domain_destroy(&ff->domain); + + destroy_stream(ff, &ff->rx_stream); + destroy_stream(ff, &ff->tx_stream); +} + +int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + + if (ff->substreams_counter == 0 || curr_rate != rate) { + enum snd_ff_stream_mode mode; + int i; + + amdtp_domain_stop(&ff->domain); + finish_session(ff); + + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); + + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i >= CIP_SFC_COUNT) + return -EINVAL; + + err = snd_ff_stream_get_multiplier_mode(i, &mode); + if (err < 0) + return err; + + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + + err = ff->spec->protocol->allocate_resources(ff, rate); + if (err < 0) + return err; + + err = amdtp_domain_set_events_per_period(&ff->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); + return err; + } + } + + return 0; +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + int err; + + if (ff->substreams_counter == 0) + return 0; + + if (amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + amdtp_domain_stop(&ff->domain); + finish_session(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + int spd = fw_parent_device(ff->unit)->max_speed; + + err = ff->spec->protocol->begin_session(ff, rate); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&ff->domain, &ff->rx_stream, + ff->rx_resources.channel, spd); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&ff->domain, &ff->tx_stream, + ff->tx_resources.channel, spd); + if (err < 0) + goto error; + + // NOTE: The device doesn't transfer packets unless receiving any packet. The + // sequence of tx packets includes cycle skip corresponding to empty packet or + // NODATA packet in IEC 61883-1/6. The sequence of the number of data blocks per + // packet is important for media clock recovery. + err = amdtp_domain_start(&ff->domain, 0, true, true); + if (err < 0) + goto error; + + if (!amdtp_domain_wait_ready(&ff->domain, READY_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; + } + + return 0; +error: + amdtp_domain_stop(&ff->domain); + finish_session(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter == 0) { + amdtp_domain_stop(&ff->domain); + finish_session(ff); + + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); + } +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + amdtp_domain_stop(&ff->domain); + + // The device discontinue to transfer packets. + amdtp_stream_pcm_abort(&ff->tx_stream); + amdtp_stream_pcm_abort(&ff->rx_stream); +} + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + guard(spinlock_irq)(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) + return -EBUSY; + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + return 0; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + guard(spinlock_irq)(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + return; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +} |
