AudioModule: validate codec2 header and bound the RX decode reads (#11036)
* AudioModule: validate codec2 header and bound the RX decode reads Two issues reachable from a crafted AUDIO_APP payload: The RX path built a temp codec2 from rx_encode_frame[3] whenever the frame header did not match ours. codec2_create returns NULL for an invalid mode byte, and the next call dereferenced it. Only decode frames that carry our own header (magic + mode) and drop the rest, so the untrusted mode byte never reaches codec2_create. The decode loop advanced by the frame size while testing only i < rx_encode_frame_index, so a payload length that was not a multiple of the frame size read past the received data and could read past rx_encode_frame. Bound each read to i + frameSize <= the received length clamped to the buffer, and clamp the receive memcpy to the buffer. Behavior change: audio frames whose codec2 mode differs from this node's configured mode are dropped instead of decoded with a temporary codec. * AudioModule: pass byte count (not sample count) to i2s_write * AudioModule: fix the mirror sample/byte bug on the i2s_read capture path
This commit is contained in:
co-authored by
GitHub
parent
166c808cdf
commit
bdf92e1183
@@ -68,23 +68,23 @@ void run_codec2(void *parameter)
|
||||
}
|
||||
if (audioModule->radio_state == RadioState::rx) {
|
||||
size_t bytesOut = 0;
|
||||
if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) {
|
||||
for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) {
|
||||
// Reject frames not carrying our own header (magic + mode); an invalid mode byte
|
||||
// would else NULL-deref via codec2_create. The bound keeps reads inside the buffer.
|
||||
const int headerLen = sizeof(audioModule->tx_header);
|
||||
const int frameSize = audioModule->encode_codec_size;
|
||||
int limit = audioModule->rx_encode_frame_index;
|
||||
if (limit > (int)sizeof(audioModule->rx_encode_frame))
|
||||
limit = sizeof(audioModule->rx_encode_frame);
|
||||
if (frameSize > 0 && limit >= headerLen &&
|
||||
memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, headerLen) == 0) {
|
||||
for (int i = headerLen; i + frameSize <= limit; i += frameSize) {
|
||||
codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
|
||||
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut,
|
||||
pdMS_TO_TICKS(500));
|
||||
// adc_buffer_size is a sample count; i2s_write wants bytes (int16_t samples).
|
||||
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size * sizeof(int16_t),
|
||||
&bytesOut, pdMS_TO_TICKS(500));
|
||||
}
|
||||
} else {
|
||||
// if the buffer header does not match our own codec, make a temp decoding setup.
|
||||
CODEC2 *tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]);
|
||||
codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2);
|
||||
int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8;
|
||||
int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2);
|
||||
for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) {
|
||||
codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
|
||||
i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
|
||||
}
|
||||
codec2_destroy(tmp_codec2);
|
||||
LOG_WARN("Audio: dropping frame with mismatched or short codec2 header");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,16 +213,18 @@ int32_t AudioModule::runOnce()
|
||||
}
|
||||
}
|
||||
if (radio_state == RadioState::tx) {
|
||||
// Get I2S data from the microphone and place in data buffer
|
||||
// Get I2S data from the microphone and place in data buffer. adc_buffer_size is a
|
||||
// sample count; i2s_read and adc_buffer_index are in bytes, so scale by int16_t.
|
||||
size_t bytesIn = 0;
|
||||
res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn,
|
||||
const size_t frameBytes = adc_buffer_size * sizeof(uint16_t);
|
||||
res = i2s_read(I2S_PORT, (uint8_t *)adc_buffer + adc_buffer_index, frameBytes - adc_buffer_index, &bytesIn,
|
||||
pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive.
|
||||
|
||||
if (res == ESP_OK) {
|
||||
adc_buffer_index += bytesIn;
|
||||
if (adc_buffer_index == adc_buffer_size) {
|
||||
if (adc_buffer_index >= frameBytes) {
|
||||
adc_buffer_index = 0;
|
||||
memcpy((void *)speech, (void *)adc_buffer, 2 * adc_buffer_size);
|
||||
memcpy((void *)speech, (void *)adc_buffer, frameBytes);
|
||||
// Notify run_codec2 task that the buffer is ready.
|
||||
radio_state = RadioState::tx;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
@@ -273,9 +275,12 @@ ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->profile->audioPermitted)) {
|
||||
auto &p = mp.decoded;
|
||||
if (!isFromUs(&mp)) {
|
||||
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
|
||||
size_t n = p.payload.size;
|
||||
if (n > sizeof(rx_encode_frame)) // bound a crafted payload to the RX buffer
|
||||
n = sizeof(rx_encode_frame);
|
||||
memcpy(rx_encode_frame, p.payload.bytes, n);
|
||||
radio_state = RadioState::rx;
|
||||
rx_encode_frame_index = p.payload.size;
|
||||
rx_encode_frame_index = n;
|
||||
// Notify run_codec2 task that the buffer is ready.
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||
|
||||
Reference in New Issue
Block a user