What YACER builds change in CoreELEC 22 nightly
Unofficial CoreELEC 22 nightly builds: everything is CoreELEC 22 nightly as they ship it, plus the 154 patches below. Release notes list what changed from one build to the next; this page lists the patches as they stand now.
Repository
Releases
Kodi 56
- 0010Let a video decoder say it holds video of its own
A codec that holds compressed data of its own breaks the assumption behind the player's stall handling: the demux queue level stops saying how much is left to play. Publish it where the codec already publishes its name. This is shared code: ProcessInfo is the only channel from the codec to the player. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0011Don't stop playback when a network share stalls
A stalled source returned no bytes, which the cache reported as the end of the file and the player acted on by stopping. NFSFile now separates the RPC layer's verdict from the file's own: a transport failure, a cancelled call, a jukebox reply or a dead socket can all succeed on a later read, and say so with -EAGAIN. The cache retries that whatever length the source advertised, so a live or chunked stream is covered too, and leaves every other failure to the stock test rather than retrying a permanent error forever. A read that finds nothing cached reports the stall through errno, as the other failures here do, and the player's own input stream turns it into the -EAGAIN ffmpeg understands. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0012Show buffering when a network file runs dry
A source that stops keeping up drained the demux queues and played on with nothing behind them. Watch the forward cache, and hold playback in the caching state while it is short, reading nothing during the hold so a read cannot stall part way through a demuxer element. The cache says when its source has no more to give, which ends the hold: a source that stops early never reaches the length it advertised, and nothing reads during a hold to find the end of the file. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0013Turn on the network buffering fix
libamcodec keeps its own stream buffer, so the demux queue empties into it and the queue's level says nothing about how much is left to play. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0014Fix a rare freeze when an NFS file is closed
The keep-alive (main thread) takes keepAliveLock then the connection lock; CNFSFile::Close takes them in the opposite order, so a close racing a keep-alive deadlocks and every later NFS call queues behind it. Skip a keep-alive round when the connection lock is busy; a later round retries. Shared filesystem code: the locks live here and nowhere else. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0015Don't freeze Kodi when an NFS server stops answering
The keep-alive does a real NFS read from ProcessSlow on the main thread; if the server stops answering, libnfs retries indefinitely; the GUI hangs. Skip that read on NFSv3, which keeps no open-file state (libnfs reconnects by itself); NFSv4 still needs it for its lease. The keep-alive list and context refresh stay, so a paused file's context is not dropped as idle. Shared filesystem code: the keep-alive lives here and nowhere else. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0016Leave video views without waiting on artwork
Leaving a video window asks the thumb loader to stop without joining it, so a slow share cannot hold the GUI thread; the next Load() joins it. A forced resource unload joins it, before the texture cache is torn down, and covers windows that were never deactivated. Shared GUI code with no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0017Don't end playback when a network share is slow
Files the player reads from a share or http now wait out a stalled read until Stop instead of passing EAGAIN on, which Matroska takes mid-element as end of file. Stall time doesn't count against the read timeout, a stop unwinds as an abort (even one made while the input opens), and the stall is logged once. Shared code: the stall reaches ffmpeg through the generic file input and avio callback; only the factory knows playback's reads. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0020Prepare for reading stream details in bulk
Move the streamdetails row to CStreamDetail conversion out of GetStreamDetails into a helper, so the bulk lookup that follows shares it (copies drift: an earlier one silently dropped newer columns). Output is unchanged. It is a private static member because only CVideoDatabase may set a stream's source and version. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0021Look up artwork for many items at once
GetArtForItem runs one query per item and the thumb loader calls it for every row, a round trip each on MySQL. Add GetArtForItems: one "media_id IN (...)" query per 500 ids, stored as GetArtForItem stores them. Each id of a chunk read gets an entry (empty = no art); a failed chunk is left out so the caller falls back to per-item lookups. Abort check before each chunk; ids are integers, never item text. Shared code, no AML counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0022Load library artwork faster
FillLibraryArt queries each item's own art on its own; only parent art is cached per load. At the first item and every 500th, read the next 500 items' own and TV show/season/set parent art (video assets excluded, cached ids skipped) into the art cache with GetArtForItems. A miss (not read ahead, failed chunk, stopped load, throw) queries as before. Only items of the loader's own list trigger a read-ahead. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0023Look up stream details for many files at once
GetStreamDetails runs one query per file, once per library row in the thumb loader. Add GetStreamDetailsForFiles: one "idFile IN (...)" query per 500 ids through the same row helper, so streams and best streams match. Each id of a chunk read gets an entry (empty = none stored); a failed chunk is left out for per-file lookup. Entries are moved as map nodes because streams point back at their owning CStreamDetails. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0024Load video, audio and subtitle info faster
Library lists come without stream details, so LoadItemCached sent one streamdetails query per row. The 500-item art read-ahead now also reads those files with GetStreamDetailsForFiles, and LoadItemCached applies an entry as GetStreamDetails would (tag, video duration, redraw only with streams). Anything not read ahead takes GetStreamDetails as before. Used entries are dropped, so at most one window is held. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0025Prepare for reading video settings in bulk
GetVideoSettings turns the settings row of a file into a CVideoSettings inline. A lookup that reads the settings of many files at once needs the same conversion, and two copies would drift apart as columns are added. Move the row conversion into a helper and have GetVideoSettings call it. The columns read and the values produced are unchanged, and the settings are still marked as specific to the file. This is shared code: the video database has no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0026Look up video settings for many files at once
GetVideoSettings runs one query per file; the thumb loader calls it for almost every movie to look for a user-set stereo mode, rarely finding a row. Add GetVideoSettingsForFiles: one "idFile IN (...)" query per 500 ids through the same row helper. Each id of a chunk read gets an entry (empty = no settings, where GetVideoSettings returns false); a failed chunk is left out for per-file lookup. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0027Check user-set 3D modes faster
The second pass queries a user-set stereo mode per item. At its first item and every 100th, read the next 100 file ids with GetVideoSettingsForFiles, only for items GetVideoSettings resolves by file id (not Blu-ray) and that DetectAndAddMissingItemData does not skip; misses query as before. The window is 100, not 500, as this pass can spend seconds per item and the read-ahead is a snapshot. Shared code, no Amlogic counterpart. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0028Stop copying video details while loading lists
The first pass copied read-ahead stream details onto the item, and the CStreamDetails copy reallocates every stream and reruns DetermineBestStreams. Add move construction/assignment that take the streams and best-stream pointers, reparent each stream and leave the source as Reset() does; the loader now moves the entry. The preferred subtitle language is now the one at read time. Shared CStreamDetails had to change: the copy is inside it. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0029Skip a slow check when details are known
The second pass called CDVDFileInfo::CanExtract, which asks the path a dozen questions and reparses the URL, before checking whether the item needs its stream details at all, which in a library it rarely does. Test that first; all three tests only read the item and the settings, so the outcome is unchanged. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0030Speed up 3D mode detection
DetectStereoModeByString compiled the 3D (then SBS/TAB) regex from the advanced settings on every call, nearly once per library item. Keep the compiled patterns and recompile only when the setting string changes. They are thread_local, since a CRegExp holds its last match state; a pattern that fails to compile is retried and logged as before. Shared code: the compile is inside the function. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 0031Pick the preferred subtitle stream faster
For subtitles, DetermineBestStreams re-parsed the best stream's language tag and refetched the preferred language on every comparison. Parse each subtitle's language once, keep the best one's parsed tag, and fetch the preferred language once per call. The decision moves to a helper shared with CStreamDetailSubtitle::IsWorseThan, so the result is identical. Shared code: the comparison and its loop both live in StreamDetails. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1020Return a safe default when a value can't be read
Get<T>() returns `T value;` after `file >> value`. If the read never runs - the open failed, or the file is empty - value comes back uninitialised, and the optional still says it has a value, so value_or() does not help. Initialise it, so a failed read gives T{}. Successful reads are unchanged. The test covers a missing node and an empty one. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com> - 1029Restore the system video path correctly after playback
Single-mode decode replaces the global "default" vfm chain and restores it on close, but GetVfmMap never matched (it ignored the "[NN]" slot index), so the saved copy was empty and close installed an empty, never-active default chain. Match the token before the brace and strip the activity marker whatever its digit. Record the override where it is made and restore only what was taken: nothing if unread or empty. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1030Fix a buffer overrun on some AV1 streams
av1_add_frame_dec_info() sized the repack buffer at data_size + 4096, but av1_parser_frame() prepends a 20-byte header per OBU; with up to data_size OBUs the output can reach 21x the payload, so an access unit of many tiny OBUs ran past the 4 KB slack. Size for that bound and NULL-check the (now larger) alloc before writing through it. AV1 hardware decode is absent on g12b, so this path doesn't run on the AM6B Plus, but the bug is real on AV1-capable Amlogic. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1031Don't keep feeding a decoder that failed to open
A failed CAMLCodec::OpenDecoder was only logged, and the player kept feeding a decoder that never started, leaving playback wedged. Close and drop the codec on failure, then skip the add-data path (without re-queueing the packet, so it cannot retry-spin) and guard Reset() against the half-open state. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1032Don't hang when moving on to the next file
CVideoPlayerVideo drains in a loop that neither waits nor reads its own message queue, and takes VC_NONE as "ask again". While draining, the VC_EOF arm waits on a queued frame count that never reaches zero, so the video thread never gets back to its queue and the stream change for the next file is never read. End the drain when the hardware has produced nothing for the decoder timeout the next arm already uses. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1050Add shared display timing helpers
Three things are about to want to know where the display is in its frame and how many refreshes a frame of content is shown for: the latency measurement, the frame scheduling and the phase alignment. Put the reads somewhere they can share before any of them is written, rather than letting three copies grow and disagree. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1051Time pictures against the real display delay
GetDisplayLatency() falls back to (NoOfBuffers() + 1) / fps, which at 23.976 is 166.8ms, and NoOfBuffers() has exactly one caller in the tree - that expression. It is arithmetic on a vestigial number, and the renderer schedules every frame against it. Report what the pipeline actually measures. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1052Line up playback with the display refresh
Where a frame lands inside the refresh period is settled by chance when playback starts and nothing afterwards moves it. Near the middle the choice of which vblank to show a frame on has half a period of slack either side; near the edge it has none, and the smallest disturbance flips it - a frame repeated, one dropped to catch up, repeated again. Hold the phase away from the edge. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1053Don't keep display data a TV reconnect may replace
The vblank read needs a crtc id, and was reaching in for the drmModeCrtc the display owns to take it - holding a pointer whose lifetime belongs to the display, across a hotplug that frees it and takes another. Publish the id instead. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1054Add lip sync to the playback timing log
The periodic report says how far the picture landed from where its timestamp asked for it, now under 2ms on this box. It says nothing about the sound, which is held to a far looser standard: the engine stops correcting a passthrough stream once its averaged error is under 30ms. Measure and report the audio too. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1055Keep picture timing the same between playbacks
The measured pipeline count was published on its first repeat and afterwards only when a lower count appeared, so which figure a session ended up with depended on the order the readings arrived in. Settle on one, and say which regime is running. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1056Log when the playback clock jumps
Every m_lost update is gated on m_classArmed, and SETTLE_SECONDS holds that down for two seconds after a resume - which is exactly when a clock step lands. So the report shows a clean sheet across the only events in a session that cost frames. Count the steps. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1057Fully correct TrueHD lip sync errors
A TrueHD passthrough stream's sync error is shrunk to 45% before the engine reports it, so that uneven arrival does not provoke a correction every second. That is reasonable for a threshold and not for the size of the step: the clock moves a fraction of the way and what is left is by construction too small to cross the threshold and try again. Step by the error that was measured. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1058Remove leftover lip sync offset after a resync
The engine calls a stream synchronised once its averaged error is inside 30ms, and in passthrough the only thing it can move the audio by is a whole IEC frame - 20ms for TrueHD - so it stops with a residue it has no way to remove. The 0.45 scaling then puts that residue under the threshold that would correct it, and there it stays for the title. Correct once more after each resynchronisation. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1059Stop passthrough audio drifting from the picture
Passthrough audio is played at whatever rate mpll0's divider happens to give. On g12b that is about 20ppm slow - 72ms of lip sync an hour - while the picture holds station to a fraction of that. Trim the audio rate to hold it against the master clock. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1060Remove leftover lip sync offset in passthrough
Holding the rate takes the drift out but says nothing about where the flat line sits: two cold starts of the same file settle tens of milliseconds apart and then each holds its own offset perfectly, which is a rate loop doing exactly its job and no more. Aim it off centre until the standing offset is gone, then let it settle back. Off keeps measuring with the ceiling at zero, so the stand-down watchdog counts only while the ceiling permits a write: an unwritten level cannot show whether the clock answers. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1061Ignore sync readings taken across an audio reopen
The sync error is the stream's playing timestamp measured against the delay the engine's own statistics describe. Re-opening the sink resets those statistics while the averaging window is still open, so what comes out is a mean over two different pipelines reported as one figure. Drop the window instead. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1062Don't cut TrueHD sync corrections short
A TrueHD stream's sync error is scaled by 0.45 before the sync loop sees it, so ordinary jitter does not provoke a correction. The adjust loop then removes the error in whole IEC frames of real silence but credits itself the full duration against the scaled error, so it stops early. Credit it in the domain it was measured in. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1063Resync audio after a TV mode change
A display reset unconfigures the sink and rebuilds it, which moves the delay every stream's sync error is measured against, and nothing tells the streams. They stay SYNC_INSYNC across the rebuild while the pipeline underneath has moved by a hundred milliseconds or more. Resynchronise them. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1064Don't leave TrueHD sync error behind
The adjust loop now spends and credits in the same units, but the band it exits on is still read in the scaled one - so on TrueHD it stops anywhere below 30ms scaled and leaves real error behind for whatever acts next. Judge the band in real time as well. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1100Play HDR10+ videos as Dolby Vision
On a Dolby Vision display, convert HDR10+ dynamic metadata to a Dolby Vision profile 8.1 RPU per frame, so the display follows the per-scene brightness. Setting "Convert HDR10+ to Dolby Vision" (Off / CM v4.0 / CM v2.9, default Off, Standard level) under Settings > System > CoreELEC. It is exclusive with SDR/HDR tone mapping and "Tone map HDR to Dolby Vision". SDR/HDR tone mapping is available again while Dolby Vision support is disabled; if it is still set when Dolby Vision support is enabled again, it wins over SDR/HDR to Dolby Vision. CoreELEC options: "Disable Dolby Vision support" moves to second place, "Disable noise reduction" defaults to on, "Override level 5 metadata to zero" defaults to off, and DV LED mode and level 5 are greyed out while Dolby Vision support is disabled. Co-authored-by: cpm-code <162544069+cpm-code@users.noreply.github.com> Co-authored-by: pannal <1359593+pannal@users.noreply.github.com> Co-authored-by: doppingkoala <doppingkoala@users.noreply.github.com> Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1101Catch up faster after a seek
CAMLCodec::GetPicture clears iFlags on the same buffer SetCodecControl wrote DVP_FLAG_DROPPED into, so an accurate seek's drop request never reached the player and the pre-roll it asked to discard was taken for the start of the stream. Set the flag where it survives, hold it across enhancement-layer packets, which the player sends with drop false, and end it on a drain. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1103Stop receivers turning dialogue down in passthrough
Rewrite dialnorm to 0 dB attenuation in passthrough AC-3, E-AC-3, TrueHD Atmos (16ch presentation) and DTS / DTS-HD / DTS:X frames, fixing the CRCs. AC-3 frames must pass their CRC first; E-AC-3 dependent substreams and DD+ Atmos (JOC, from addbsi or the stream profile) are never touched; TrueHD and DTS EXSS are only rewritten after their CRC checks, DTS core DNG only for VERNUM 6/7. One multi-select setting in the CoreELEC section picks the formats (read on codec open): AC-3/E-AC-3, TrueHD Atmos and DTS, all selected by default. Ported from pannal/xbmc 91e6499007..02169a0bd6 plus the dialnorm part of 451fecaadb. This is shared audio-engine code with no Amlogic hook, so it cannot live in Amlogic files. Co-authored-by: jamal2362 <15986930+jamal2362@users.noreply.github.com> Co-authored-by: pannal <1359593+pannal@users.noreply.github.com> Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1104Fix a rare crash when playback stops during drain
GetPicture reads the queued frame count straight from m_amlVideoFile while CloseDecoder can be releasing it: m_opened is cleared before the file is dropped, so a video thread already past that check dereferences a null pointer. Take the same copy under m_amlVideoFileMutex the other callers take; no file means nothing is queued, which ends the drain. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 1105Retire the timing callbacks with the decoder
The renderer ticks the latency and genlock loops from a queued frame, which holds the codec past CloseDecoder. m_opened was a plain bool read on that thread, so a tick could pass the check and publish after Forget had run, carrying one decoder's measurements into the next. Make it atomic and take a mutex around the retirement and both callbacks. Signed-off-by: allolive <160342668+allolive@users.noreply.github.com>
- 2201Validate VP9 superframe index sizes
vp9_update_frame_header() parses the VP9 superframe index into a plain int array with size[i] |= buf[p] << (cur_mag * 8), so with mag == 4 and a top byte >= 0x80 the frame size comes out negative. The only guard is the total_datasize > dsize test after the loop, which a negative sum passes. A six byte packet d8 f8 ff ff ff d8 gives frame_number 1, mag 4 and size[0] == -8; need_more is then 2, av_grow_packet() succeeds, and the rewrite loop calls memmove(fdata + 16, old_framedata, (size_t)-8). With several frames a large positive size can cancel a negative one, leaving oldframeoff before the start of the packet, so the header write and the memmove land at a stream-chosen negative offset. The packet comes straight from the demuxer, so a crafted or damaged WebM/MKV reaches it. Parse each entry into a uint32_t and, before storing it, check in int64_t that the running sum plus this entry still fits in dsize - index_sz (known non-negative from the earlier mag_ptr < 0 test). A packet failing the check returns PLAYER_SUCCESS without rewriting, as this function already does for a short index or a mismatched second marker. need_more is also checked before av_grow_packet() as a backstop. Checked by replaying the parser and rewrite loop standalone over random packets; valid superframes give identical offsets before and after. Not smoke-tested against real VP9 superframe content on hardware.
- 2202Stop parsing on payload size exceeding remaining bits
HevcSei.cpp compares m_payloadSize, which is a byte count, against AvailableBits(), a bit count, so a payload up to 8x the remaining data passes the check. SkipBits() then runs past the end and the parse loop can keep going on garbage instead of stopping. Compare against AvailableBits() / 8 instead. AvailableBits() itself underflows to a huge unsigned value once m_posBits passes length * 8; clamp it to 0. m_payloadType is accumulated in 255 increments, so widen it from uint8_t to uint32_t to match m_payloadSize. Needs a malformed stream to trigger. Not exercised on hardware.
- 2203Bound and copy the VVC NAL length rewrite
The VVC branch of Convert() rewrote the demuxer packet in place with no bounds check on the 4-byte NAL lengths. A length >= 0xFFFFFFF8 drives the signed position negative while the loop test still holds, so the next pass writes four bytes before the heap buffer, and AV_RB16(pos + 4) can read past the end of a short packet. The rewrite is not idempotent either, and the packet is re-queued on ordinary backpressure (CAMLCodec::AddData returns false at 95% buffer fill), so its own start codes get parsed as lengths. Convert into an av_malloc'd private buffer as the H264/HEVC paths do, all arithmetic in size_t: a NAL is taken only if nal_size <= size - pos - 4, so pos <= size throughout; a packet whose lengths do not tile it exactly is dropped with a debug log. The VVC block is a CoreELEC addition only the AML codec reaches. Triggering needs a malformed stream; untested on hardware (no FMT_H266 on g12b or S7D, so the branch is unreachable there).
- 2204Parse vvcC per ISO/IEC 14496-15 with bounds checks
vvc_add_frame_dec_info() walked the VvcDecoderConfigurationRecord with no bounds checks, memcpy'ing a 16-bit unit_size taken straight from the file for each of up to 255 arrays. It also misread the layout, so valid files are affected too: ptl_sublayer_level_present_flags exists only when num_sublayers > 1 but was always consumed, leaving a single-sublayer record one byte out of step; the flags were tested with ">> 1" not ">> i", ptl_num_sub_profiles assumed 0, ptl_present_flag ignored, num_nalus read as a start code, DCI/OPI arrays unhandled. Rewrite it to check the remaining bytes before every read and copy and reject a bad record with PLAYER_FAILED, count sublayer level bytes by popcount since writers disagree on bit placement, and free any header left from a previous parse (a leak when Reset() re-parses Annex-B extradata). Checked in a host ASan harness - the old code overreads 2577 bytes on a valid vvcC from ff_vvcc_write(); not run on hardware, VVC being gated off on g12b and S7D (no FMT_H266 in either chip table).
- 2205Pair dual-layer DV by timestamp and cap the queue
AddData pairs base-layer and enhancement-layer packets purely by arrival order, and m_packages is unbounded. On a profile 7 dual-track title a missing half leaves an orphan at the head of the list and every later frame is decoded against its neighbour's EL and RPU; if the EL stops arriving, BL packets are queued a frame per frame until kodi is killed. Pair by timestamp instead: each queued half carries its pts and dts and matches the first entry of the other layer within 2 ms (both tracks share one format context and start-time offset). Entries queued ahead of the match are dropped, and the list is capped at 48 - two seconds of one layer at 24 fps - after which, if nothing ever paired, it falls back to arrival order. Freeing a queued half goes through one helper, taken on the keyframe-wait path too, and a failed AlignedMalloc now drops the packet instead of being memcpy'd through. Not tried on hardware.
- 2206Parse VVC extradata per spec with bounds checks
BitstreamConvertInitVVC walked the VvcDecoderConfigurationRecord with no bounds checks and a layout that does not match ISO/IEC 14496-15: it assumed ptl_present_flag, always consumed the sublayer flags byte, skipped no general_sub_profile_idc, and required num_nalus == 1 with no DCI/OPI case, so records ffmpeg writes are rejected. Non SPS/PPS arrays advanced the pointer untested, so a vvcC declaring 255 arrays of unit_size 0xffff walks 64K past the allocation per pass: a corrupt file crashes at stream open. Parse it as ffmpeg's cbs_h266 and vvcc_write define it, checking the bytes remaining before every field, length and skip, and freeing the parameter buffer on every failure path. Sublayer level flags are counted by popcount (ffmpeg's writer packs them low, its reader high), clamped to num_sublayers - 1. Open now needs two bytes before reading in_extradata[1] and clears m_start_decode on a bad record, so the converter falls back to per-packet detection instead of feeding MP4 length prefixes as start codes. Shared file, but the VVC branch is reached only from DVDVideoCodecAmlogic here, and is dead on g12b and S7D today: hardening ahead of the S905X5 VVC decoder. Checked in a standalone harness against seven conformant vvcC records and fuzzed inputs under ASan/UBSan; not exercised on hardware.
- 2207A failed DV property read is not an enabled core
CAMLCodec::CloseDecoder decided whether a Dolby Vision core has to be powered down from the raw result of aml_get_drmProperty("dv_enable"). That returns -1 when the cached connector state is not connected, or when the property is absent, and -1 converts to true. The close then enters the loop that spins while dv_status != 0, which reads -1 for the same reason, so it runs for the whole m_decoder_timeout - five seconds by default, up to sixty - with the VideoPlayer thread asleep inside CloseStream. The writes after the wait are no-ops anyway, since aml_set_drmProperty is gated on the same connected check. m_decoder_timeout is only assigned by OpenDecoder, which runs lazily from AddData, but Close calls CloseDecoder on any non-null m_Codec, so a stop during caching or before the first keyframe reaches the loop with the member uninitialised and the bound indeterminate. Compare dv_enable and dv_status against > 0, so a failed read means "not enabled" / "stop waiting"; dv_video_on already compared == 1. Give m_decoder_timeout and the two buffer-level members default member initialisers. Valid values are unaffected: 1 stays enabled, 0 disabled. Reasoned from source; the standby trigger was not reproduced on hardware. - 2211Destroy DRM property blob after use
The std::string overload of CAMLDRMUtils::aml_set_drmProperty() creates a DRM property blob with drmModeCreatePropertyBlob(), passes the blob id to the unsigned int overload, and returns without destroying it. The blob stays on the DRM file's blob list until userspace destroys it or the fd closes, and CAMLDisplay keeps m_fd open for the life of the process. This is hit on ordinary playback: AMLCodec::CloseDecoder() sets "dv_debug" three times ("enable_fel 0", "enable_mel 0", "force_unmap") per close while the connector is connected, and DV open adds two more. Destroy the blob once the property is set, as aml_set_drmDevice_active() already does for its MODE_ID blob. The meson_crtc.c handler copies the blob contents during the set-property ioctl, so nothing refers to it afterwards. Not exercised on hardware: someone should confirm on g12b that DV FEL/MEL and force_unmap still take effect. The kernel side of this leak (a missing drm_property_blob_put() in meson_crtc.c) is fixed separately; until that lands the object stays allocated despite this destroy. - 2214Don't integrate while the loop is switched off
With the audio-lead setting at Off (the default) ReadSlew() returns nullopt and m_levelMax stays zero, so nothing is written to audio_sdm_trim and the loop only measures. Settle() still ran the integrator, clamping m_want against MAX_LEVEL_RATE rather than the ceiling. With the ~20ppm residual measured here m_want hits that limit after ~17 fifteen-second decisions; nothing was applied, so the drift never answers, m_clamped counts up, and eight decisions later the watchdog logs "the clock is not listening - standing down" and latches m_stopped for the rest of the title, ignoring the setting if it is turned on mid-title. Skip the integrator when off: hold m_want at rest, reset m_clamped, log the drift at debug. The branch sits after the median and plausibility test, so the measurement is unchanged. m_levelMax is either 0.0 or at least 2.0 (SLEW_MIN 5ppm, MAX_LEVEL_RATE 2.0, STEP_RATE 30ppm), so <= 0.0 is exactly "off". A genuine stand-down still latches. Not exercised on hardware.
- 2215Look up keep-alive context under the connection lock
CNfsConnection::keepAlive fetched the nfs_context with getContextFromMap() before taking the connection lock, and dereferenced it (nfs_get_version, then nfs_lseek/nfs_read on NFSv4) only after the try_lock succeeded. getContextFromMap holds only openContextLock, so in that gap CNFSFile::Open - which holds the connection lock - can hit NFS4ERR_EXPIRED and call Deinit(), whose destroyOpenContexts() runs nfs_destroy_context() on every entry. keepAlive then uses freed memory. The window is a few instructions, but it opens exactly when a lease expires. Deinit also cleared m_KeepAliveTimeouts without keepAliveLock while CheckIfIdle iterates that map under it. Take the try_lock first and look the context up after it; hold keepAliveLock around the clear in Deinit. Lock order is unchanged. Deinit still frees contexts that live CNFSFile objects point into; that is left alone here. Reproducing needs an NFSv4 share and a lease expiry, which has not been run on hardware.
- 2217Pick the trim node by SoC, adding S7D and S6
The trim loop hard-codes the g12a driver's mpll0 trim node, /sys/module/amlogic_clk_soc_g12a/parameters/audio_sdm_trim. On S905X5 the audio clock comes from the HIFI PLLs, trimmed by the s6 driver (plain S905X5) or the s7d one (S905X5M); the g12a handler refuses the write with -ENODEV, the read back never moves, and the loop disables itself. Resolve the node once from the SoC id: AML_S6 and AML_S7D take their own node, any other or unidentified family keeps the g12a path unchanged. Not by which node exists: with both drivers built in both module parameters appear whether or not the driver probed. STEP=2 and the limit of forty carry over to S6/S7D as an assumption. Needs the kernel patches adding the node (1112 for S6, 2084 for S7D); not exercised on S905X5 hardware.
Kernel drivers 63
- 1050Meson: let the audio clock be sped up or slowed slightly
Passthrough audio is clocked from mpll0 and the receiver recovers its own sample clock from it, so the rate mpll0 runs at is the rate the audio is heard at. Measured on g12b it walks about 20ppm against the display, and in passthrough the audio engine has nothing finer than a whole 20ms IEC frame to answer with. Expose the sdm field so the rate can be trimmed instead. One step is about 15ppm, and the sigma-delta already dithers this divide, so the field can move under a running stream. Nothing else is touched: not n2, not sdm_en, not the gate.
- 1051Add a diagnostic reading of video output timing
The driver's timing reports are all taken before the video post-processor, so they miss the frames held by the Dolby Vision core and the scaler. Add a read-only node sampled in one go with interrupts off: the CLOCK_MONOTONIC base DRM stamps vblanks on, the encoder frame count, the current output line, and the post-processor CRC where present. Nothing in the driver acts on it.
- 2053Hdr10+: clamp ST 2094-40 row/col counts to array size
parser_hdr10_plus_medata() reads num_rows/num_cols for both ST 2094-40 actual-peak-luminance grids as 5-bit fields, so a stream can ask for up to 31x31, but tgt_sys_disp_act_pk_lumi and mast_disp_act_pk_lumi are u16 [25][25]. The fill loops are bounded only by those stream-supplied counts, so a 31x31 grid writes flat element 780 of 625, about 312 bytes past the array; for the mastering grid roughly 186 bytes land past the global vframe_hdr_plus_sei in module .bss. getbits() does not help: on a short record it returns -1 and leaves the value untouched, and no caller checks it. The debug dump reads the same grids back with the stored counts, so it over-reads too. Only a malformed stream triggers this. Clamp the value with min_t()/ARRAY_SIZE() right after each getbits(), before it reaches the struct field and the loop bound. Not compile- or stream-tested here.
- 2054Edid: bound DV VSVDB and HDR metadata copies
The EDID parser sizes copies into struct rx_cap from the sink's 5-bit block length byte, and the sysfs dumps reuse those lengths, so a malformed EDID corrupts rx_cap: up to 32 bytes are copied into the 27-byte dv_info.rawdata and into the 7-byte hdr_info.rawdata, and in the dynamic block a u8 data_end wraps to 255 while type_length - 3 as u32 turns a length byte of 0..2 into a ~4G-iteration write over optional_fields[28]. The hdr_cap sysfs dump loops of_len - 3 and wraps the same way. Clamp the DV and static copies to sizeof(rawdata), return early on an empty dynamic block, skip descriptors shorter than their type field or longer than the block remainder, and clamp the sysfs dumps. dv->length keeps the sink's raw value; the version checks and the hdmitx20/21 and amdv consumers compare it against real VSVDB sizes. vout*_serve still loops j < of_len, now bounded at 29. Not exercised on hardware.
- 2055Auge: tdm: fix chmap control lifetime and put handler
"Playback Channel Map" was created in DAI prepare and removed from trigger on every STOP/SUSPEND/PAUSE_PUSH, without card->controls_rwsem, so a concurrent control ioctl can be inside the get handler while the element is freed. The control also outlives a substream opened and closed without starting, and the put handler dereferenced substream->runtime unchecked; there "matches" was set only inside the loop, so a channel count other than 2/4/6/8 read an uninitialised slot and matched_layout, which feeds the HDMI InfoFrame channel_allocation field, could be garbage. Prepare also leaked one aml_chmap per cycle. Create the control once per PCM, publish handlers, WRITE access and private_data under controls_rwsem, and chain private_free so the aml_chmap dies with the control; trigger only resets chmap_layout. The put handler rejects a missing substream/runtime, snapshots runtime->channels and derives the match from the loop index. The control now stays present after the first playback. Not exercised on hardware.
- 2056Hdcp: reset KSV/ReceiverID cursor before rebuilding list
The downstream KSV/ReceiverID list lives in one 640-byte buffer ((127 + 1) * sizeof(struct hdcp_ksv_t)); p_ksv_next is the write cursor and every writer advances it unchecked. ksv_reset_fifo() rewinds it on auth start, reset, auth failure and HDCP2.x REAUTH_REQ, but not where the list is rebuilt in place: each RPT_RCVID_CHANGED interrupt runs assemble_ds_ksv_lists(), appending dev_cnt * 5 + 5 bytes, and HDCP1.x REAUTH_RI_MISMATCH on a non-repeater sink appends 5 more. A downstream HDCP2.x repeater re-sends ReceiverID_List (RxStatus READY, no REAUTH_REQ) on every topology change of its own, so with a one-device list the 65th event writes past the allocation and corrupts the kmalloc-1k slab. Rewind the cursor in both places, clamp dev_cnt to HDCP2X_MAX_DEV, and clamp the HDCP1.x FIFO byte count to the remaining fifo_byte_counter, whose u16 subtraction would otherwise wrap. Not exercised on hardware.
- 2057Gem: return a fresh sg_table from prime_get_sg_table
meson_gem_prime_get_sg_table() returned the gem's own table: for an is_dma gem that is &a->table inside the meson_dma_heap_attachment from dma_buf_map_attachment(). The DRM PRIME helpers own what this hook returns and drm_gem_unmap_dma_buf() calls sg_free_table() and kfree() on it, so exporting a dumb/MESON_GEM_CREATE buffer and letting another device attach and unmap frees the heap's scatterlist and kfree()s an interior pointer; the later detach then frees it again. The ION branch did copy, but with vmalloc() (the helper kfree()s) and sized by nents, not orig_nents. Add meson_gem_dup_sg_table(): kmalloc + sg_alloc_table(orig_nents), copying page/length/offset per entry. Use it on both branches. The unsupported path returns ERR_PTR(-EINVAL); drm_gem_map_dma_buf() only checks IS_ERR(). Not exercised on hardware: stock Kodi imports heap-gfx buffers rather than exporting meson GEMs. UVM (afbc/secure) exports never reach this hook.
- 2058Pip_alpha: bound layer_id and win_num from sysfs
pip_alpha_store() uses the first two sysfs values unchecked. win_num sizes the second parse (param_num = win_num * 4 + 2); parse_para() takes no buffer size, so that count is the only bound on writes into the stack array int parsed[66]. "0 20 " plus 80 numbers stores 82 ints, 16 words past the frame. The fill loop also runs to win_num, past the MAX_PIP_WINDOW (16) alpha_win arrays, and win_en |= 1 << i is undefined at i >= 32. Separately layer_id indexes vd_layer[MAX_VD_LAYER] (3), so "3 1 0 0 0 0" writes into adjacent globals the vsync ISR reads. Return -EINVAL unless 0 <= layer_id < MAX_VD_LAYER and 0 <= win_num <= MAX_PIP_WINDOW, and size parsed[] as MAX_PIP_WINDOW * 4 + 2 (the same 66). alpha_win is memset first, retiring the uninit_use_in_call suppression. The pre-existing race with the ISR reading vd_layer[] is left alone. video_cut.c has the identical function and is fixed the same way, but needs CONFIG_AMLOGIC_ZAPPER_CUT and is not built here. Not exercised on hardware.
- 2059Make the CMA capture buffer per-fd
amvideocap_open() allocates a cma_max_size MB CMA block per open but stores it in the global getgctrl()->phyaddr; release() frees that global, i.e. the newest opener's block, not its own. With two overlapping opens of /dev/amvideocap0 (a grabber while Kodi screenshots), A's close frees B's block while B still captures into, reads and mmaps it; B's close double-frees it and A's 34 MB block leaks, eventually failing decoder allocation. The -EMFILE and kmalloc error paths leaked too. Keep phyaddr/vaddr only in priv, check the allocation for 0 (the old code did not), free on every error path, and free priv->phyaddr in release(). All users already read priv, and a remap_pfn_range VMA holds a file reference, so a block is never freed while mapped. Not exercised with two concurrent openers on hardware. amvideocap_mmap() still does not bound vm_pgoff against the block size; that is left alone.
- 2061Count consecutive RPU parse errors, retry later
parse_sei_and_meta_ext_v2() refuses to parse anything once dv_inst[dv_id].err_parse_cnt exceeds 10. multi_mp_process() failures raise it, but nothing lowers it except dv_inst_map(), so the limit counts total parser failures over a decoder session, not failures in a row. Eleven damaged RPUs anywhere in a long DV title (a bad splice, a remux error, a truncated TS segment) latch the gate for the rest of the session: every frame then reuses the previous md/comp sizes, dynamic L1/L2 tone mapping stays frozen, the parse-in-advance path falls back to SDR, and an unratelimited pr_err fires once per vsync. Zero the counter when the parser succeeds, and let one call through every DV_PARSE_RETRY_INTERVAL (30) frames while suspended so the state can recover. The per-call pr_err goes; the existing pr_dv_error at the failure site still reports each retry. Only the stb 2.6 v2 parser path has this latch; v1 and hw5 keep calling the parser after a failure. Triggering it needs deliberately damaged RPU payloads, so it has not been reproduced on hardware here.
- 2062Ext_v2: bound AV1 T35 header check and debug dump
parse_sei_and_meta_ext_v2() only checks a record's announced length against the rest of the aux buffer, then for an AV1 T35 OBU dereferences p[0..9] for the Dolby ITU-T T.35 signature and p[10..12] for the RPU size; a record of length 1 at the end of the buffer reads up to twelve bytes past aux_buf + aux_size, and the existing "p + 13 + rpu_size" guard runs only after those reads. Require size >= 13 first, so short AV1 records take the non-RPU path as a non-matching signature already does. The debug dump under debug_dolby & 4 iterates i < size, the record length, but prints from the 1024-byte meta_buf; unlike the HEVC branch the AV1 branch never clamps, so a Dolby record over 1024 bytes dumps stack to dmesg. Iterate over rpu_size and stop at the buffer end. Both need a malformed stream; ext_v1's identical code is untouched. Not exercised on hardware (AV1 DV is S905X5, not g12b).
- 2063Fbdev: fix buffer lifetime and dma_buf refcounting
The gem owns one dma_buf reference, but alloc_fb_gem() took another on every open past the first and nothing dropped it (fb0's 70778880-byte buffer on g12b reaches file count 6 as Kodi reopens /dev/fb0), and free_fb_gem() put it twice on the ION path, freeing the gem behind its refcount and leaking the vmap. set_par rewrote the fb geometry and screen_size before freeing and reallocating, so a failed oversized FBIOPUT_VSCREENINFO left fb_gem, bufp[0] and screen_base NULL under a valid geometry for a later mmap or FBIOGET_OSD_DMABUF to dereference; screen_size was a u32 product, so 4096x1048576 wrapped to 0. Alloc, install and release are split out: set_par checks the format, sizes in u64 against fix.smem_len's u32 and allocates before touching the old buffer; release_gem() unmaps its own vmap, puts the dma_buf only on the ION path and uses drm_gem_object_put(); mmap() calls vma_set_file() so the mapping pins the dma_buf, as remap_pfn_range() takes no page references. The buffer pointers are swapped under info->mm_lock, which fbmem already holds across ->fb_mmap in the pinned kernel. Not exercised on S905X5 hardware.
- 2064Dmabuf_manage: check dma-buf ops and fix a dma_buf leak
dmabuf_manage_get_handle(), _get_phyaddr() and _import() read dbuf->priv as a struct dmabuf_manage_block without checking this driver exported the buffer. An fd from another exporter (dma-heap, uvm, ion) has the first 24 bytes of its own priv read as paddr/handle/extend and part of that returned to user space: an info leak, an out-of-bounds read if that object is smaller, or a NULL deref if it keeps no priv. The two *_get_dmabufinfo() helpers separately "goto error" on a block->type mismatch, below the dma_buf_put(), leaking a reference. Add dmabuf_manage_to_block(), returning priv only for our own buffers, and use it in all five sites; get_handle and get_phyaddr now return -EINVAL for a foreign fd, import still accepts one and returns the full untruncated address. Both mismatch branches goto error_fd. Build-tested only: nothing in CoreELEC opens /dev/secmem.
- 2065Only clear the zero HDR10+ VSIF in work_hdr
When HDR10+ output stops, amcsc sends an SDR DRM packet and then the all-zero HDR10+ VSIF, which sets hdr_status_pos = 4. The DRM packet schedules work_hdr; hdr_work_func sleeps 1.5 s and then clears the vendor infoframe slot whenever hdr_status_pos is still 4, without checking which VSIF is there and without taking a lock. Nothing on the Dolby Vision start path resets hdr_status_pos, so a DV start inside that window (playlist change at the same mode) has its fresh DV VSIF wiped until amdv resends it; an HDMI 1.4b or real HDR10+ VSIF likewise. Read the live VSIF IEEE OUI and clear only if it is 0 (zero VSIF or empty slot), or CUVA_IEEEOUI on hdmitx21, where hdmitx_set_cuva_hdr_vs_emds also sets pos = 4. Check and clear under hdev->tx_comm.edid_spinlock, which the other VSIF setters already hold; the msleep and uevent stay outside it. hdmitx_set_hdr10plus_pkt does not take the lock, so a real HDR10+ VSIF landing right after the OUI read can still be cleared, costing one frame. The hdmitx21 side has not been run on S905X5 hardware.
- 2066Hw: protect interrupt st_data between IRQ and bottom half
intr_t.st_data carries an hdmitx21 interrupt event from the hard IRQ to its bottom half with no locking. intr_status_save_and_clear() ORs the status register into st_data and then write-1-clears the hardware, so the event lives only there; the bottom half and the sw handlers do an unprotected "read st_data; st_data = 0". hdmi_intr_wq is not WQ_UNBOUND, so the IRQ can preempt the kworker between that load and store and the newly ORed bit is lost: a dropped HPD edge leaves hpd_state stale until the next edge, a dropped HDCP2 AUTH_DONE/RCVID_CHANGED stalls until the 2 s/3 s timeout. The window is a few instructions - it needs contact bounce or an interrupt burst to hit. Add a file-local spinlock, intr_st_lock (nested outside the leaf register lock), around every st_data access, with a new intr_st_fetch_clr() helper for the read-then-zero sites. Callbacks still run with the lock dropped. hdmitx21 does not probe on g12b (of_match covers t7/s5/s1a/s7/s7d/s6 only), so this is compile-only there; not exercised on S905X5 hardware.
- 2067Hdcp: synchronise disable with the HDCP interrupt bottom half
hdmitx21_disable_hdcp() cancels the hdcp_wq works, then hdcp_mode_set(0) clears p_hdcp->hdcptx_enabled only after hdcptx_reset(), which sleeps in ddc_check_busy() and the hdcptx2_auth_stop() poll loop. The HDCP bottom half runs on a different workqueue (hdmi_intr_wq) that the disable path never cancels, and the CP2TX interrupt mask is never closed. An AUTH_FAIL arriving in that window sees hdcptx_enabled still 1 and re-arms timer_hdcp_auth_fail_retry for 200 ms after the cancel; SKE_SEND likewise re-arms timer_hdcp_rpt_auth. The stray work then runs hdcptx_reset() with the HDCP clock gate already closed and no lock held. Clear hdcptx_enabled before hdcptx_reset() and before hdcp_cancel_works(), refuse to arm work while it is false (the cancel form is still allowed through), and return early from the bksv_poll_done, ddc_check_nak and auth_fail_retry handlers. The flag stays a plain bool, so this narrows the arming race rather than closing it; the handler guards are the backstop. Not done here: hdcp_enable_intrs(0), flushing work_internal_intr, and ddc_mutex around the DDC abort/reset. hdmitx21 does not probe on g12b (of_match lists t7/s5/s1a/s7/s7d/s6), so the change is compiled but dormant there and is not exercised on hardware.
- 2068Run EDID tracer post-processing outside edid_spinlock
hdmitx_common_get_edid() calls hdmitx_common_edid_tracer_post_proc() while holding edid_spinlock with IRQs disabled. On an EDID with a bad header, post_proc logs HDMITX_EDID_HEAD_ERROR, which with CONFIG_AMLOGIC_MEDIA_RESMANAGE=y reaches resman_notify_error_info(): two mutexes and two kzalloc(GFP_KERNEL) calls, i.e. sleeping in atomic context in the hotplug worker. It is the only post_proc event that gets there. Needs a bad DDC read - HPD high but garbage or zeroes, as after a TV or AVR leaves standby, or on a flaky cable. Move the call to just after spin_unlock_irqrestore(). Parsing and hdmitx_set_hdr_priority() stay under the lock, so edid_spinlock readers still see a consistent rxcap, and post_proc reads only fields that hdmitx_set_hdr_priority() does not write. Tested on g12b only.
- 2069Hdmi: pass a bounded EDID copy to the connector
meson_hdmitx_get_modes() and the two cec_notifier_set_phys_addr_from_edid() calls passed the raw hdmitx buffer tx_comm->EDID_buf, a fixed EDID_MAX_BLOCK * 128 = 1024 byte array inside struct hdmitx_common. Both consumers size the EDID as EDID_LENGTH * (1 + edid->extensions) without validating it, so an extension count of 8..255 in byte 0x7e makes DRM read up to 32 KB from that 1024 byte field and copy the excess - rxcap, mutexes, the rest of the device structure - into the connector EDID blob, readable by any DRM client via GETPROPBLOB. It takes a malformed sink EDID, a corrupted DDC read, or a root write to the sysfs edid "load" attribute. Use the copy hdmitx_common_read_edid() already builds: it holds valid_mutex and returns at most the blocks the buffer holds. Clamp edid->extensions to that, fixing the block 0 checksum, and free the copy after use. Only the size is bounded; a concurrent DDC re-read can still leave the content inconsistent, which needs a separate change. Not exercised on hardware.
- 2071Crtc: send commit event immediately when vblank is off
am_meson_crtc_atomic_flush() arms the pending commit event on the next vblank. If drm_crtc_vblank_get() fails it parks the event in the single amcrtc->event slot, drained only by the VPU vsync handler. vblank_get fails when am_meson_crtc_atomic_enable() takes its VMODE_MAX early return (vout rejected the mode name): the crtc stays active with vblank off, and with vout at "null" the vsync interrupt may never fire. The event is then never sent, a second flush overwrites the first, flip_done never completes and an attached OUT_FENCE is never signalled, freezing a client that waits without a timeout (Kodi's eglClientWaitSyncKHR/FOREVER). Complete the event immediately instead, with drm_crtc_send_vblank_event(); flush already holds dev->event_lock with irqsave as that helper requires. The armed path is unchanged. Display stays black after the failed modeset; this only stops the hang. Needs an already-failed modeset; not reproduced on hardware.
- 2072Writeback: reject fbs with no dma_buf and drain job queue
meson_writeback_capture_picture() does get_dma_buf(meson_fb->bufp[0]->dmabuf) unchecked. Only am_meson_gem_alloc_ion_buff() sets that field, so a gem imported through PRIME leaves it NULL and a master committing WRITEBACK_FB_ID with such an fb oopses in the capture kworker. Reject it in atomic_check (-EINVAL on the commit), with a backstop in the capture path. Rejecting beats using the imported dma_buf: vdin uses PFN_PHYS(page_to_pfn(sg_page( sgt->sgl))) over the whole buffer, so a scattered import would DMA into the wrong memory. vdin_capture_picture() fails before it takes the buffer, so drop our reference there and return -EIO instead of vdin's raw -1. Finally, the work item read one shared ->fb, which a second commit queued while the work was pending overwrote, leaving a job on job_queue unsignalled; loop over job_queue and capture job->fb instead. Not exercised on hardware here.
- 2074Reject negative layer/vpp indices in sysfs stores
parse_para() uses kstrtoint(), so twelve /sys/class/video stores in video.c accept a negative layer index: "parsed[0] < MAX_VD_LAYER" passes for -1. Writing "-1 5" to force_skip_count writes force_skip_cnt[0xffffffff]; the pre_[hv]scaler_*, force_pre_scaler, vd_attch_vpp and vdx test_pattern stores write pre_scaler[-1] or vd_layer[-1]. vd_attch_vpp also stored parsed[1] into vd_layer[].vpp_index unbounded, and the vsync path indexes 4-entry tables (rdma_func[], vsync_cnt[], vpp_hold_line[]) with it, so "0 9" reads out of bounds in interrupt context. Add a ">= 0" bound to the twelve checks, matching how these stores already treat a too-large index, and return -EINVAL for a vpp index outside [0, VPP_MAX). In-range values, including PRE_VSYNC (3), behave as before. These are root-only debug nodes; not exercised on hardware.
- 2075Av_probe: count vsyncs at ISR entry, reconcile with line
av_probe reported the driver's global vsync_count as its vsync field, but that counter is incremented only in misc_early_proc(), well inside vsync_isr_in() -- after the RDMA over-field record, Dolby Vision and per-layer setup, several milliseconds with DV enabled on g12b. Passes that return earlier (video_proc_lock contention, overrun_flag, video_suspend, DEBUG_FLAG_VSYNC_DONONE, the first_irq RDMA workaround) never increment it, so it drifts permanently behind the encoder, and between the encoder wrapping and the interrupt reaching misc_early_proc() it is paired with a small line number. Any offset built on the triple is then a whole frame out, intermittently. Stamp a dedicated counter, timestamp and encoder line at the top of vsync_isr()/vsync_fisr(), published under a seqcount, the way DRM stamps vblank. ktime_get_mono_fast_ns() because the same code is compiled into the FIQ handler under CONFIG_AML_VSYNC_FIQ_ENABLE, and raw_write_seqcount_*() because the caller is already non-preemptible. That leaves only interrupt entry latency, so the count is reconciled against the line using the refresh period and field height from the current vinfo; the thresholds (three quarters of a period, half a field) leave about 300 lines of margin at 1080p60 and 150 at 1080i50, and the correction is suppressed when the last stamp is over two periods old. The thresholds are derived from the encoder line behaviour the low-latency path already assumes, not measured; not exercised on hardware. vsync_count itself is untouched.
- 2076Drop stale original_vf after light unreg/reset
common_vf_light_unreg_provider() handles PROVIDER_RESET and PROVIDER_LIGHT_UNREG: the provider has taken all its frames back. It copies the on-screen frame into ins->local_buf and points cur_buf at it, but unlike the full unreg path never clears original_vf, which still points at a reclaimed frame. On the next vsync with nothing queued, recv_common_dequeue_frame() takes the repeat-frame branch, which only tests cur_buf != original_vf, and hands the reclaimed vframe back to the layer; common_toggle_frame() then puts it, which can duplicate an entry in the decoder recycle queue. A decoder flush on seek reaches this. Clear original_vf on light unreg, and skip the repeat-frame restore while cur_buf is still the local copy; the layer code already has a "cur_buf == &local_buf -> keep frame" case. The !ins->active term leaves full-unreg behaviour (kept last frame dropped on stop) as is. Not hardware-tested: CoreELEC's Kodi uses the amvideo vfm map, so the video_render receivers are not exercised by normal playback here.
- 2077Serialise vpu_delay_work_flag updates under delay_work_lock
The VDx_MEM_POWER_ON/OFF and VPU_VDx_CLK_SWITCH macros update vpu_delay_work_flag under delay_work_lock; the event-bit writers (do_vpu_delay_work(), update_primary_fmt_event(), set_amdv_delay_work_flag(), the vsync LAYERx_CHANGED sets) do not. Stopping video sets the VD1 memory power-off bit and starts a 100-vsync countdown. If video restarts inside that window, enable_video_layer() clears the bit under the lock while the delayed work is doing an unlocked "flag &= ~EVENT" and writes back a stale value with the bit still set; VD1, AFBC, scaler and film-grain memory are then powered down while VD1 is on screen. The reverse interleaving loses a power-off request instead. Racy, not reproducible on demand. do_vpu_delay_work() now copies and clears the event bits under the lock and acts on the copy; a new VPU_DELAY_WORK_FLAG_SET() macro replaces the unlocked sets. vpu_work_process() still reads the flag unlocked to decide whether to schedule; a torn read there costs one deferred schedule. Not exercised on S905X5 hardware.
- 2078Lock the amvideocap capture request against the vsync ISR
amvideocap_capture_one_frame_wait() keeps `struct amvideocap_req req` on its own stack and publishes &req into vd_layer[0].capture_frame_req through ext_register_end_frame_callback(); the vsync hard-IRQ handler consumes it in ext_frame_capture_poll(). The only synchronisation is an unlocked atomic_read()/atomic_set() pair on capture_use_cnt, and the ISR reloads capture_frame_req three times (test, ->data, ->callback), so a concurrent clear can turn the last reload into a NULL dereference in hard IRQ context. Kodi's CScreenshotAML captures with a 40 ms deadline, so the timeout path is routine: the waiter withdraws the request and reuses that stack frame while the ISR may still hold &req or be inside the callback, and once userspace closes the fd amvideocap_release() kfree()s the privdata the ISR is writing. Add capture_req_lock, a spinlock taken with irqsave, around every access to capture_frame_req and capture_use_cnt: registration and withdrawal, consumption (now via a local snapshot of the request), and the layer on/off resets, which move out of the VIDEO_LAYER_ON()/VIDEO_LAYER_OFF() macros into ext_frame_capture_set_state(). The ISR holds the lock across the callback, so a withdrawal that returns proves the request is no longer in use. Lock order is video_onoff_lock -> capture_req_lock; the callback path does not sleep (canvas_pool uses a spinlock, ge2d queues with GFP_ATOMIC and does not wait). Two pre-existing hazards are left alone: the non-blocking GE2D blit can still finish after amvideocap_release() freed the CMA buffer, and AMVIDEOCAP_IOW_SET_CANCEL_CAPTURE still does not cancel. Not exercised on hardware yet.
- 2079Only parse AV1 T.35 records that carry a DV RPU
Aux record type 0x14 is used "for both dv and hdr10plus": the AV1 decoder hands up every ITU-T T.35 OBU under that tag and only rewrites the buffer down to a single record when the OBU really is a DV RPU (payload starting b5 00 3b 00 00 08 00 37 cd 08). An HDR10+ OBU, or any other T.35 provider, stays in the buffer as a raw 0x14 record. In parse_sei_and_meta_ext_v1/_v2 and the hw5 copy such a record fails the ten byte Dolby header test and falls into the "HEVC dv meta in sei" else branch, which forces *src_format = FORMAT_DOVI, prefixes the payload 00 00 00 and hands it to the RPU parser, then breaks out of the scan. The parse cannot succeed - an AV1 RPU has to be unpacked from the EMDF container - so the frame is classed as Dolby Vision, reuses the previous frame's md/comp buffers and bumps err_parse_cnt; after eleven such frames the per instance guard rejects all metadata for the rest of the session, including real RPUs. The break also hides a genuine DV record sitting behind the offending one in the same aux buffer. Run the HEVC SEI fallback only for type == DV_SEI; an AV1 T.35 record without the Dolby header is skipped (p += size; continue), leaving src_format and err_parse_cnt alone. is_amdv_frame gets the matching test: an AV1 T.35 record counts as DV only if it is long enough, stays inside the aux buffer and starts with that ten byte header. Needs a DV signalled AV1 stream whose frames carry a non-DV T.35 OBU, or a client that never sets negative_dv. Not fixed here: err_parse_cnt is still never cleared after a good parse or at flush. Untested on hardware - g12b has no AV1 hardware decode, so the changed branches are unreachable there and no S905X5 box is available.
- 2080Clear EL state and reset core on DV-to-SDR fallback
When the STB control path in dovi.ko rejects a DOVI input with -2 ("dv source but metadata checked as el"), amdv_control_path() retries with input[0].src_format forced to FORMAT_SDR, but leaves el_flag and the in_md/in_comp RPU and composer pointers and sizes as copied from dv_inst. dv_core1a_set()/dv_core1b_set() derive composer_enable and el_41_mode from el_flag and el_halfsize_flag, so the retry can program core1 with the EL composer enabled for an input just declared SDR. The retry also skips the multi_control_path(&invalid_m_dovi_setting) core reset that every other source/destination/mode change here performs. Clear el_flag, in_md, in_comp and their sizes, reset the core as a format change does, then re-commit as SDR. All these fields are rebuilt from dv_inst on every call, so nothing carries to the next frame. If the SDR retry also fails, the existing video_width = 0 path forces a res_change reset next frame, so no loop forms. el_halfsize_flag is left alone: with el_flag 0 it is not read on this path. Reaching the EL case needs a stream whose metadata is flagged as an enhancement layer; el_flag is otherwise zeroed by amdv_parse_metadata_v2_stb() unless the debug-only enable_mel is set. Not yet exercised on S905X5 hardware. - 2081Debug_store: free the original kstrdup buffer
cvbs_debug_store() duplicates the sysfs write buffer into p with kstrdup(), then hands &p to strsep() six times to split argv[0..5]. strsep() advances the pointer, so at DEBUG_END p is no longer what kstrdup() returned, yet that label does kfree(p). With six or fewer tokens p is NULL, so the duplicate leaks on every write; with seven or more - e.g. echo "r c 0 1 2 3 4" > /sys/class/cvbs/debug - p points into the middle of the allocation and kfree() gets an interior pointer. Three switch arms also return bare instead of goto, skipping the free, and kstrdup() is unchecked, so on allocation failure argv[0] stays NULL and the following strcmp() dereferences it. Keep the original in buf_orig, return early if the allocation fails, let p stay the cursor, turn the five bare returns into goto DEBUG_END, and free buf_orig there. The node is 0644, so the invalid free needs a root write of a long debug command; the leak happens on any write. Not built or booted here.
- 2082Bound vdac_parse_param to the parm[] array size
vdac_debug_store() declares "char *parm[3]" and passes it to vdac_parse_param(), which does "parm[n++] = token" for every space- or newline-separated token with no bound on n. A write of more than three tokens, e.g. "echo 'w reg_cntl0 1 2 3 4' > /sys/class/amvdac/debug", stores past the array over reg_val, tmp and the stack canary. The attribute is 0644, so this is root-only, not reachable from playback. Pass the array size in and stop at n < max; the caller passes ARRAY_SIZE(parm), which also drops the bogus "(char **)&parm" cast. Also free buf_orig on the "!parm[0]" return, return -ENOMEM when kstrdup() fails, and goto vdac_store_err when kstrtouint() fails in the "w" branch instead of writing an uninitialised reg_val to a register - so a malformed value now returns -EINVAL. Not built or run on hardware.
- 2083Meson: pll_v4: write frac/od of 0 on in-place rate change
When m, n and en already match the target, meson_clk_pll_v4_set_rate() updates frac and od in place and returns 0. It wrote frac only if the target frac was non-zero and od only if the target od was non-zero, so a request needing frac = 0 or od = 0 was dropped while set_rate still returned success; recalc_rate reads the hardware back, so the clock keeps reporting the old rate. S7D hifi_pll_table has {150, 0, 0} = 1800M and {150, 0, 2} = 450M, same m and n: from 450M, clk_set_rate(hifi_pll, 1800000000) leaves od at 2. Drop the !!frac and !!od guards. frac is written whenever it differs, behind MESON_PARM_APPLICABLE(&pll->frac) as the full path does; that also stops the old code reading the uninitialised local frac on v4 PLLs with no frac field. od is compared and written every time. No relock is added. Affects only meson_clk_pll_v4_ops users (S7D gp0/hifi/hifi1, S6 PLLs); g12b uses meson_clk_pll_ops. The S7D audio rates in use today (491.52M, 451.584M, non-zero frac, od = 2) get identical register writes. Not tested on hardware; needs an S905X5 board to exercise. Left alone: meson_clk_pll_od_or_frac_correct() still compares only m, n and en, and meson_clk_pll_get_param_frac() can still return 1 << width. - 2085Roll back suspend state when di_suspend aborts
di_suspend() (.suspend_late for dim-g12b and dim-s7d) sets DI_SUSPEND_FLAG, lowers the vpu_clkb rate, runs di_clear_for_suspend() and gates clk_b off before polling dpre/dpst/dct_can_exit() 20 times; on timeout it returns -1 with none of that undone. A failed suspend_late aborts the suspend, and the PM core then never calls the matching resume, so the flag is never cleared: dim_api_reg(), the DI_POST_REG_RD/WR helpers and the di ioctl path all refuse, and deinterlacing stays dead until reboot. The DI kthreads are freezable and already frozen here, so one frozen mid-frame keeps the state non-idle for all 20 polls. Undo the state on the failure path and return -EBUSY instead of -1 (an observable errno change for /sys/power/state). Not exercised on hardware: the branch needs DI to be stuck at suspend time.
- 2086Di_que_list: drain a local kfifo copy
di_que_list() snapshots a queue by memcpy'ing the live struct kfifo header of fifo[qtype] into the per-channel fifo[QUE_DBG], then drains that copy with an unbounded while (kfifo_out()) loop. After the first call fifo[QUE_DBG].data aliases fifo[qtype].data, so the buffer di_que_alloc() gave QUE_DBG leaks and di_que_release() later kfifo_free()s the same buffer twice. fifo[QUE_DBG] is also unlocked shared state: a debug lister racing the DI task can retear in/out mid-drain and run the loop past the caller's arr[MAX_FIFO_SIZE + 1] on the stack. Copy the header into a local struct kfifo, drain that, stop at MAX_FIFO_SIZE entries, and report *rsize as the count actually copied. With no concurrent writer the output is identical to before. Not exercised on hardware.
- 2087Dmabuf_manage: fix double free on dma_buf_fd failure paths
get_dmabuf() hands the dmabuf_manage_block to dma_buf_export() as exp_info.priv, after which dmabuf_manage_buf_release() owns it and frees it on the last put. Four ioctl paths free it a second time when dma_buf_fd() fails after a successful export: dmabuf_manage_alloc_dmabuf(), dmabuf_manage_extend_alloc_dmabuf(), dmabuf_manage_extend_export_dmabuf() and dmabuf_manage_register_channel() all fall through their own unwinding after dma_buf_put(). In ioctl context the put is deferred to task_work, so the ioctl frees first and the release callback frees the same memory again - a double kfree, a second codec_mm_free_for_dma() of a range that may already be reused, and in register_channel a release_channel() driven by a freed channel. Triggering it needs dma_buf_fd() to fail (in practice -EMFILE) in a process that can open /dev/secmem; normal playback never gets there. Clear dbuf->priv before the put on those four paths. The release callback guards every branch with "if (block)", so it becomes a no-op and the function's own unwinding stays the single owner. This also removes a self-deadlock in register_channel, which holds g_secure_pool_mutex across the put while release_channel() takes it. Two smaller fixes in the same paths: extend_alloc_dmabuf's error_alloc freed block->paddr, always 0 for an extend block, leaking the CMA allocation at block->extend_paddr; and extend_export_dmabuf returned 0 from its dma_buf_fd() exit, which user space would read as fd 0. Code-level fix, not exercised on hardware. extend_export_dmabuf's two other failure exits still return 0; left alone here.
- 2088Dmabuf_manage: bound mmap to the buffer
dmabuf_manage_mmap() ignored the caller's mapping: it passed the block's own paddr_size as the length to remap_pfn_range() and always started at the base of the buffer. A mapping shorter than the buffer therefore populated page tables past vma->vm_end, into whatever follows the VMA, and a non-zero vm_pgoff was silently ignored, so the caller got the wrong pages. Map what was asked for: reject an offset past the end of the buffer, or a length that would run past it, then remap vm_end - vm_start bytes starting at the requested page offset. Mapping an exported block is still allowed; that is a policy question, not part of this overrun fix. Reached only through mmap of a dmabuf_manage fd, so no decode or display path is affected. Not run on hardware.
- 2089Codec_mm: reject sizes that overflow the page_cnt shift
codec_mm_heap_do_allocate() passes PAGE_ALIGN(len) / PAGE_SIZE to codec_mm_alloc_for_dma() as an int page count, which calls codec_mm_alloc() with the int byte size page_cnt << PAGE_SHIFT. The dma-heap core only page-aligns len and rejects zero, so a root DMA_HEAP_IOCTL_ALLOC of 4GiB + 4KiB wraps the shift to 0x1000 and codec_mm returns one page while exp_info.size stays 4GiB + 4KiB. An importer that vmaps it runs codec_mm_heap_do_vmap() with npages = 1048577 over a pages[] array with one entry filled. Reject len > (INT_MAX & PAGE_MASK) (0x7ffff000) with -EINVAL before allocating, and return 0 from codec_mm_alloc_for_dma(_ex) for a negative or oversized page_cnt, before the shift. No in-tree or userspace caller asks for near 2GiB. Not compile-tested.
- 2090Keeper: restore user when re-keeping an unmasked handle
codec_mm_keeper_mask_keep_mem() reuses an existing slot when the handle is already kept: it returns the old keep_id and drops the extra reference it just took, but leaves the slot's user count and pending free deadline alone. If that slot was already unmasked, user is 0 and the deadline is armed, so the new keeper gets a valid id while codec_mm_keeper_monitor() still frees the slot, dropping its only reference. video_keeper unmasks every keep id with a 120 ms delay on each newly displayed frame; a decoder reset re-keeping the same handle inside that window leaves amvideo scanning out memory the keeper no longer holds, so the VPU later reads freed memory. Restore user to 1 and clear the deadline when re-keeping a slot whose user has fallen to 0. Since the caller decides to free with the slot unlocked, codec_mm_keeper_free_keep() now takes force and repeats the test under mgr->lock, also bailing out on a NULL handle so mgr->num cannot go negative. Not fixed: two keepers holding the same handle at once still share one user count, so the first unmask frees for both. Untested on hardware: the race needs the same handle re-kept inside the 120 ms window.
- 2091Rdma_check_conflict: test adr[k], not adr[i]
rdma_check_conflict() records a register another RDMA channel has already queued for this frame, so a later read-modify-write builds its new value from the pending value instead of the live hardware one. The slot loop runs over k (0..MAX_CONFLICT, 32), but its emptiness test reads rdma_info.rdma_reg.adr[i], the other channel's index; the two assignments below it use [k]. With adr[i] zero every conflict lands in slot 0, overwriting whatever was recorded there; with adr[i] non-zero nothing is recorded at all. Either way the dropped register falls back to the live VCBUS value and the read-modify-write can clobber bits the other channel queued for the same vsync - an intermittent wrong-bits glitch in a shared VPP/blend register, not a memory-safety issue (adr[i] was always in bounds, channel_num <= 16). Fix the test to use adr[k]. The table can now hold more than one live entry; entries are retired in rdma_mgr_isr only when the register reads back equal to the tracked value, so a register that never converges keeps its slot until reset - previously true of slot 0, now of up to 32. The unlocked access to rdma_info.rdma_reg is pre-existing and unchanged. Not exercised on hardware: triggering it needs two RDMA channels queueing the same register in one frame.
- 2092Source_meta_copy: fix reverse word count, cap core3
reverse_dv_meta() computes the dword count as (byte_size + 3) / 4, but prepare_dv_meta() packs byte 0 into the first dword and the rest four per dword, so the count is 1 + (byte_size + 2) / 4. Whenever byte_size % 4 != 1 the last 1-3 bytes are not rebuilt; for a 70-72 byte core output that includes byte 69, and the fixed 70-byte memcpy then copies leftovers from the static reversed_meta_buffer. Fix the count and reject a zero size. The 128-dword and in->size limits still cap writes at 509 bytes of the 512-byte buffer. source_meta_copy() only checked the reversed size was non-zero yet always copied ETSI_META_OFFSET (70) bytes; require at least that many. Source DM metadata of 814 bytes or more gives md_reg3.size > 204 (MAX_CORE3_METADATA), after which dv_core3_set() drops every metadata register write for that frame and the TV keeps stale metadata. Skip the override in that case and keep the blob's own md_reg3; truncating the extension block list would break its framing. It needs an RPU with very many extension blocks to trigger. Not exercised on S905X5 hardware.
- 2093Debug_store: free buf_orig on error, bound pri_input
amdolby_vision_debug_store() kstrdup()s its input into buf_orig, but the 35 argument-parsing failures do a bare "return -EINVAL;" and leak it. parse_param_amdv() fills absent tokens with "", so "echo amdv_crc > /sys/class/amdolby_vision/debug" is enough. The node is 0644, but the function is also reached from drivers/drm/meson_crtc.c via the DRM dv_debug blob property, where a client looping on a malformed string leaks without bound. Convert those returns to "ret = -EINVAL; goto out;" with out: before the existing kfree(); success still returns count. Also bound "pri_input N", stored unchecked into a static int used to index dv_inst[] and m_dovi_setting.input[]. Reject outside [0, NUM_IPCORE1), tested on the long so values above INT_MAX are rejected before truncation. Not exercised on hardware.
- 2094Vrr: take vrr_para.lock with irqsave
vrr_para.lock in hdmi_tx_vrr.c is taken with plain spin_lock in both directions: hdmitx_set_vrr_para() writes conf_params from process context (hdmitx_set_fr_hint(), holding only vout_mutex), while hdmitx_get_vrr_params() reads it from the vrr_vsync hard interrupt handler. If that interrupt lands on the writing CPU before the spin_unlock, the handler spins forever on a lock its own CPU holds. The window is a few instructions wide, so a real hang is rare, but lockdep flags the HARDIRQ-ON-W / IN-HARDIRQ-W usage immediately. Use spin_lock_irqsave()/spin_unlock_irqrestore() in both functions; they are the only users of this lock, so the class becomes uniformly irq-safe. Neither is on a per-frame path. The surrounding unlocked reads of conf_params.vrr_enabled are pre-existing and left alone. No S905X5 hardware here, so this is not exercised on a board.
- 2095Vid/aud mute store: update krefs atomically
aud_mute_store() and vid_mute_store() copy hdev->kref_audio_mute or hdev->kref_video_mute into a local atomic_t, change the copy and write it back. kernfs only serialises writes on one open file, so two processes each writing '1' can both read 0 and both store 1, losing a mute reference. The count and the path mask could also disagree with no race: every '0' write called the unmute op, so after '1', '1', '0' the count was 1 and vid_mute_show reported 1, but VIDEO_MUTE_PATH_1 was cleared and the output live; the next mode set then re-muted via restore_mute(). Update the counters in hdev in place under a new file-static mutex per node, and unmute only when atomic_dec_if_positive() returns <= 0, as hdmitx20 does. N '1' writes now need N '0' writes to unmute. Node names and output formats are unchanged; nothing in CoreELEC or Kodi writes them. restore_mute() still reads the counts unlocked - pre-existing, and path 0 sets no mask bit. hdmitx20 (g12b/S922X) is untouched. Not run on S905X5 hardware yet.
- 2096Bail out of async commit when swap_state fails
meson_atomic_commit()'s async_update branch calls drm_atomic_helper_swap_state(state, true), throws the return value away, and runs meson_atomic_helper_async_commit() regardless. With stall=true swap_state() can return -ERESTARTSYS from its wait_for_completion_interruptible() loop, before anything is swapped, so plane->state is still the old state. meson_osd_plane_async_update() / meson_video_plane_async_update() then do "plane->state->fb = new_state->fb" - a self-assignment on the normal path, but here it stores the new fb into the live old state without a reference and drops the old fb's. The fb is later freed while the plane still points at it. Requires a signal to arrive during the ioctl. Check the return value: clean up the planes, drop the reenter count and return the error without touching plane->state. Also release the CRTC commit mutex on the prepare_planes() error return, which previously left it held for good (not reachable today: meson_plane_prepare_fb() always returns 0). The in-tree caller meson_async_atomic_ioctl() already propagates a non-zero result, so an interrupted async update now drops the frame instead of corrupting refcounts. The plane async_update hooks still assign rather than swap fb pointers; that is left alone. Nothing on CoreELEC issues MESON_ASYNC_ATOMIC, so this has not been exercised on hardware.
- 2097Bound the VPU debug parser and overwrite_reg table
The VPU "debug" node exists twice, as the sysfs attribute /sys/class/drm/card0/vpu/debug (meson_sysfs.c) and as the debugfs file of the same name (meson_debugfs.c). Both call a local parse_param() that stores every whitespace-separated token with "parm[n++] = token" and no upper bound, into a "char *parm[8]": a write of more than eight tokens stores past the end of a stack array. Both writers then dereference parm[1]..parm[4] without checking they are present, so an empty or short write NULL-derefs. The "wv"/"wvb" commands also append to overwrite_reg[256]/overwrite_val[256] with a bare reg_num++, so the 257th distinct register writes past both arrays, which the commit path in meson_vpu_pipeline.c then reads back into VPU registers. Pass the destination size into parse_param(), stop at that many tokens and return the count; reject a write with no token with -EINVAL; NULL-check each parm[n] before kstrtoul()/kstrtouint(); refuse a new table entry with -ENOSPC at ARRAY_SIZE(overwrite_reg). Both nodes are root-only, so this is a memory-safety fix, not a privilege boundary. The missing synchronisation between these writers and the commit-path readers of reg_num/overwrite_reg[] is left alone. Not exercised on hardware.
- 2098Fbdev: reject FBIOGET_OSD_DMABUF with no fb
am_meson_drm_fbdev_ioctl() runs container_of() on helper->fb without checking it. struct am_meson_fb has base at offset 0, so a NULL fb yields a NULL meson_fb and the following bufp[0] read faults. helper->fb is NULL before the first modeset and after the fbdev framebuffer is released, and the ioctl is reachable from userspace throughout. Return -ENODEV instead. The dma_buf refcount and fd handling in the same ioctl were already fixed by 2063; this patch adds only the missing framebuffer check and applies on top of it. Not exercised on hardware.
- 2099Plane: release reserved fd on export-sync-file errors
am_meson_dmabuf_export_sync_file_ioctl() reserves a descriptor with get_unused_fd_flags(O_CLOEXEC) before validating its arguments, then returns directly on two failures: arg->dmabuf_fd <= 0, and dma_buf_get() failing. Neither calls put_unused_fd(), so each such call burns one descriptor in the caller's file table; only the sync_file_create() failure reaches the existing err_put_fd label. A process holding DRM master that repeats the ioctl with a bad dmabuf_fd eventually hits EMFILE, and the slots return only when it exits. Send both failures through err_put_fd. The returned errno is unchanged (-EINVAL), so there is no uapi change. Reaching the leak requires a DRM-master caller passing a bad fd; Kodi does not call this ioctl, so this is hardening. Compile- and read-verified only, not exercised on hardware.
- 2100Crtc: take present_fence.lock around the fence slot
Each crtc has a single present-fence slot, amcrtc->present_fence. spin_lock_init() sets up present_fence.lock, but neither user holds it: the MESON_CREAT_PRESENT_FENCE ioctl (registered with flags 0, so drm_ioctl() takes no lock) and meson_drm_signal_present_fence() in the vsync handler. Two concurrent ioctl callers on one crtc both see a NULL slot, both install an fd, and the second store overwrites the first: the first fence's driver reference leaks and its waiters never wake. The handler also re-reads pre_fence->fence for the test, signal, put and clear, so a store in between can signal one fence and put another. Take the lock with spin_lock_irqsave in both paths, since the handler runs in hard-irq context. The ioctl claims the slot under the lock and only then calls fd_install(), which cannot be undone; a losing caller fputs the sync_file, releases the fd and returns -EEXIST as before. The handler takes the fence out and clears the slot under the lock, then signals and puts it after unlocking, because that lock is also the fence's own lock. Nothing sleeps under it and it never nests with event_lock. Not compiled and not run on hardware. The race needs two concurrent callers; Kodi does not use this ioctl. A fence left in the slot at unbind is still never signalled - pre-existing and not addressed here.
- 2101Free the layer reservation in amvideo_release
amvideo_release() only cleared file->private_data. A client that takes a layer with AMSTREAM_IOC_ALLOC_LAYER and then exits or crashes without calling AMSTREAM_IOC_FREE_LAYER leaks the reservation: alloc_layer() is the only place LAYERn_BUSY is set in the static layer_cap, and free_layer(), reached only from AMSTREAM_IOC_FREE_LAYER, the only place it is cleared. Every later ALLOC_LAYER for that layer then returns -EBUSY and QUERY_LAYER keeps reporting it taken, until reboot. Free it from release(), which runs on the last fput and so covers both an orderly exit and a crash. private_data already holds &glayer_info[n], whose layer_id is initialised by vpp_disp_info_init(), so release() derives the id the same way FREE_LAYER does, under the same video_layer_mutex. A double free is harmless: free_layer() just returns -EINVAL. Nothing in CoreELEC issues ALLOC_LAYER (Kodi leaves private_data NULL), so the leak needs an HWC-style client and this was reviewed, not run on hardware. video_cut.c has the same defect but is arm32-only and is left alone.
- 2102Fix swapped try_free_keep_vdx() parameter order
try_free_keep_vdx() is declared as (int flags, u8 layer_id), but every call site passes the keeper slot first and the flag word second. Both parameters are integer types, so this compiles silently; the vendor left a /* FIXME */ on two of the calls. The callers have it right: they pass keep_frame_id (0, 1, 2 or 0xff), and 0xff only makes sense against the MAX_VD_LAYER guard. Worst case, _videopip_set_disable() with 0xff arrives as flags=0xff, layer=0: it unmasks VD1's keep ids and calls free_alloced_keep_buffer() on memory VD1 may still be scanning out. Swap the parameters instead of editing ten call sites, and reorder try_free_keep_video(), the one caller written for the old order; its own behaviour is unchanged, so media_modules-aml is unaffected. Locking and the MAX_VD_LAYER guard are untouched. Not fixed: layer{1,2,3}_used is computed regardless of layer_id. Read and reasoned only, not soaked. - 2103Read canvases before programming 3D MIF addresses
canvas_update_for_3d() declares cs0/cs1/cs2[2] as uninitialised stack arrays. When the vframe passes the right-eye picture as canvas indices (vf->canvas1Addr != -1), only the [1] entries are filled by canvas_read(). With cur_dev->mif_linear set, the function still hands &cs0[0]/&cs1[0]/ &cs2[0] to set_vd_mif_linear_cs(..., 1), so stack garbage reaches the field-1 base-address and stride registers of the VD MIF: garbage picture in field modes, or a bus fault on a secure region. Reaching it needs a mif_linear SoC, layer 0 in MVC/3D mode, and a decoder passing canvas indices rather than canvas configs; no shipped S7D decoder does that today. g12b has mif_linear=0 and is unaffected. Fill the [0] entries from cur_canvas_tbl[0..2], the left-eye planes that canvas_update_for_mif() just wrote, and the same planes the canvas-config branch already programs into field 1. Untested on hardware: no S905X5 here, and g12b never enters the block.
- 2104Use per-path blackout policy in pipx_vf_unreg_provider
pipx_vf_unreg_provider() handles the provider-unregister event for both PIP paths: pip_receiver_event_fun() calls it with path_index 1 and pip2_receiver_event_fun() with path_index 2. The blackout test used blackout[2] unconditionally, i.e. the PIP2 policy, whichever path was being torn down. Everywhere else blackout[] is indexed by VFM path, and the two values are separately settable (blackout_pip_policy writes blackout[1], blackout_pip2_policy writes blackout[2]); the pr_info() below the test already prints blackout[path_index], so log and action could disagree. With blackout_pip_policy=1 and blackout_pip2_policy=0, a PIP1 unregister skipped safe_switch_videolayer() and try_free_keep_vdx(), leaving VD2 on with its keeper frame. It is a policy error only: the keep-failed branch below still switches the layers off if vf_keep_current_locked() fails. Use blackout[path_index]. path_index is only ever 1 or 2 here, both below MAX_VD_LAYERS (3). With the shipped defaults both PIP policies are 0, so the expressions are equal and behaviour is unchanged; the difference appears only once userspace sets them differently. Kodi writes only blackout_policy and runs no VFM_PATH_PIP provider, so this has not been exercised on hardware.
- 2105Crtc: put dv_debug property blob reference
meson_crtc_atomic_set_property() looks the dv_debug blob up with drm_property_lookup_blob(), which returns a reference the caller must drop with drm_property_blob_put(). The dv_debug branch never does, on either exit: it returns 0 after the kmalloc failure and again after copying the string out. The reference DRM core takes for the property value is a separate, balanced one. The property is attached to every CRTC with no SoC condition. Kodi hits it routinely - AMLCodec sets enable_fel/enable_mel on open and enable_fel 0, enable_mel 0 and force_unmap on close, one fresh blob per call - so each call strands a blob plus its data for the life of the device. A few dozen bytes a few times per playback: slow growth, no corruption, no crash. Drop the reference on both paths; blob->data is only read by the memcpy that precedes the put. Not compiled or run here.
- 2106Serialise DV_IOC_SET_DV_CONFIG_DATA buffer swap
DV_IOC_SET_DV_CONFIG_DATA rebuilt the global cfg_data/bin_data buffers with no serialisation: it vfree()d the old buffer, vmalloc()ed a new one into the global, then copy_from_user()ed into it. amdolby_vision_ioctl() takes no lock and any number of opens are allowed, so two threads on /dev/amdolby_vision can double-free the same pointer, or one can sleep in copy_from_user() while the other frees that buffer underneath it. cfg_size/bin_size are separate globals, so a reader can pair one thread's size with another's buffer. Allocate and copy into a local pointer first, then swap pointer and size under a new dv_config_data_lock mutex, freeing the old buffer there. The lock also covers the only consumer, cp_dv_pq_config_data(). A failed vmalloc or copy now leaves the previous configuration intact; no uapi, command number, size limit or return code changes. Needs a privileged local caller with two concurrent threads to trigger; not exercised on hardware.
- 2107Release EL vframe on BL/EL PTS mismatch
In amdv_parse_metadata_v1() and amdv_parse_metadata_v2_stb(), with a separate "dveldec" enhancement-layer decoder and toggle_mode 1, the EL vframe is taken off the queue with dvel_vf_get() before its pts_us64 is compared with the base layer's. On a match it goes to amdvdolby_vision_vf_add() and is later released; on a mismatch the else branch only prints "bl(...) not found el(...)" and drops the pointer, so the frame never returns to dveldec's small pool. A handful of mismatches starve it and the base layer stalls waiting for the EL. Put the frame back with dvel_vf_put() in that branch, the same get-then-put idiom amdv_wait_metadata_v1() uses when it skips a stale EL. The branch needs FLAG_CHECK_ES_PTS (0x400) in the dolby_vision_flags module parameter plus the dual-decoder VFM path, neither of which is set by default, so this is untested on hardware.
- 2108Bounds-check dv_inst index in the control path
amdv_control_path() takes the instance id straight off the vframe (id = vf->src_fmt.dv_id) and, inside the "if (vd_proc_info)" blocks, compares and writes dv_inst[id].video_width/video_height before any bounds check; the only dv_inst_valid(id) check comes later. dv_inst[] is a static array of NUM_INST (15) entries, so a negative id reads and writes the memory just before the array, from vsync context. dv_id can be negative: dv_inst_map() sets *inst = -1 when all 15 slots are mapped, and the V4L2 decoders and v4lvideo copy that -1 into vf->src_fmt.dv_id. Nothing on the way down replaces it, and need_cp can be set by a display-size change alone, so the call happens even when metadata parsing failed for that frame. Guard both vd_proc_info blocks with dv_inst_valid(id), and make the two debug prints report 0 for an out-of-range id. Behaviour past the existing dv_inst_valid(id) check is unchanged: an invalid instance is not skipped, so input[i].valid and valid_video_num stay as they are. Not reproduced on hardware - the trigger needs all 15 instances mapped at once, i.e. an instance leak; this is a bounds check placed where the code already expected one.
- 2109Vout_trim_string: handle empty and all-space strings
vout_trim_string() tests str[len - 1] before checking len, and sets end = str + len - 1 and walks backwards over spaces with no lower bound. The sysfs mode stores reach it with user text. vout2_mode_store() has no disable_modesysfs gate, so "echo > /sys/class/display2/mode" passes "\n": the newline strip leaves len = 0, end points at mode[-1] on the caller's stack, and the loop reads that byte, zeroing it and walking further down if it happens to be 0x20. All-space input does the same once the spaces are cleared. The final strcpy() also copies between overlapping buffers. Only strip the newline when len > 0, let end point at the NUL, stop the trailing-space loop at start, and move the text with memmove(). Valid mode strings trim as before; empty and all-space input now give "", which the callers reject like any unknown mode. No sysfs ABI change. Checked against the old code in a userspace ASan harness; not run on hardware.
- 2111Meson: mask frac sign bit, fix g12a hdmi_pll_dco frac width
The legacy __pll_params_to_rate() in clk-pll.c treats the fractional field as sign-magnitude - bit (width-1) selects add or subtract - but multiplies the raw field, sign bit included, into frac_rate. With weight 2^(width-2) the sign bit alone contributes 2 * parent_rate to the subtraction, so any PLL with that bit set reads about two parent clocks low. Mask it off before the multiply. g12a_hdmi_pll_dco also declares frac width 16; HHI_HDMI_PLL_CNTL1 is 19 bits (lcd_clk_g12.c: len 19, frac_range 1<<17, sign_bit 18), as fixed_pll_dco and hifi_pll_dco in the same file already declare. Together the two defects make hdmi_pll_dco report ~1.2% low for the rates hdmitx20 programs directly, e.g. m=0xf3 frac=0x18000 reads 5784 MHz instead of 5850 MHz. Reported rate only: the clock is read-only (meson_clk_pll_ro_ops), so nothing is programmed through CCF. gp0_pll_dco has the same width discrepancy but is left alone - it is writable, where the width would also change set_rate. Not confirmed yet against a clk_summary dump on the g12b box.
- 2112Mtask: pop a whole command record on fifo overflow
dim_fcmd_s::fifo is an untyped byte kfifo of sizeof(struct mtsk_cmd_s) * MAX_KFIFO_L_CMD_NUB = 8 * 32 = 256 bytes, and every normal access moves exactly one 8-byte record. The drop-oldest path in mtask_send_cmd()/mtask_send_cmd_block() instead pops sizeof(unsigned int), so it removes half a record, compares the 4 bytes returned against sizeof(struct mtsk_cmd_s) and always returns false. The new command is dropped anyway and the read offset is left mid-record: kfifo_is_full() never trips again and every later mtask_get_cmd() decodes cmd/nub/page bits out of the previous record's blk_flg_s word, so blk_polling() allocates and releases blocks with garbage parameters for the life of the channel. Pop sizeof(struct mtsk_cmd_s) so the fifo stays record-aligned, and atomic_dec() fcmd->doing for the discarded command, which blk_polling() would otherwise never decrement. The fifo is full when this runs, so at least 31 increments remain outstanding. The previously unreachable PR_ERR("lost cmd") and err_cmd_cnt++ now fire once per dropped command. Reaching the overflow needs 32 commands queued while the memory thread is stalled (e.g. a slow codec_mm_alloc_for_dma under CMA pressure), which was not reproduced here; the fix is reasoned from the code. tb_task.c carries the same bug for struct tb_task_cmd_s and is deliberately left alone. - 2113Fix bypass NULL check in di_fill_output_buffer_mode3
di_fill_output_buffer_mode3() reads ndis1 from buffer->private_data but guards the bypass branch with IS_ERR_OR_NULL(pintf). pintf is set two lines earlier to &pch->itf, so the test is always false and the branch is dead; the function then dereferences ndis1->header.index unconditionally and oopses on a bypass buffer, which carries no DI local buffer (ndis_fill_ready_bypass() queues the caller's own input with DI_FLAG_BUF_BY_PASS and no ndis attached, in tmode 3). Test ndis1 instead. empty_input_done() is the right disposal: it is the caller's own input buffer, and the bypass path clears nins->c.ori before recycling, so it is completed exactly once. The warning becomes the debug-gated dim_print now the branch is live, so a sustained bypass cannot spam the log once per frame. Reachable only through /dev/di_v4l (root-only CRC test node), so it is latent robustness, not a crash seen on a box; not exercised on hardware. Range-checking ndis1->header.index is left out as separate hardening.
- 2114Bound dump_vfm_state output with scnprintf
dump_vfm_state() built its output with plain sprintf() into a caller supplied buffer without checking the room left; provider_list() and receiver_list() did the same. Its live caller is map_show(), which hands it the one-page sysfs buffer of /sys/class/vfm/map. vfm_map_add() accepts up to VFM_MAP_COUNT (40) maps of VFM_MAP_SIZE (10) 31-char names, so about eleven root writes of a maximum-length map push the listing past 4096 bytes and a later read writes past the sysfs page. Pass the size down and use scnprintf(buf + len, size - len, ...): PAGE_SIZE for a caller-supplied buffer, DUMP_BUFFER_SIZE for the local_dump_buf path. Output is unchanged for any state that fitted before. Compile-tested only; not exercised on hardware. Not fixed here: vfm_map_num_store() still sets vfm_map_num from sysfs with no bound against VFM_MAP_COUNT.
- 2115Range-check vfm_map_num sysfs store
vfm_map_num_store() assigned the parsed value straight to vfm_map_num with no bounds check, and /sys/class/vfm/vfm_map_num is CLASS_ATTR_RW(), so root can write it. vfm_map_num is the only bound every walk of the fixed 40-entry vfm_map[VFM_MAP_COUNT] uses. A value above the populated count reaches NULL slots that vf_check_node() and vf_get_provider_name_inmap() dereference without a test; a value above VFM_MAP_COUNT walks off the array into the globals behind it and treats them as struct vfm_map_s pointers. Those lookups run on the vf_get/ vf_peek/vf_put path from the vsync interrupt. Reject values outside [0, VFM_MAP_COUNT] and values naming an unpopulated slot with -EINVAL, under the existing spinlock that vfm_map_add() takes when it publishes a slot. val also becomes int, which is what kstrtoint() expects. Not exercised on hardware.
- 2116Free trace buffers only after in-flight vf_get/vf_put finish
vf_unreg_provider() kfree()d prov->traceget / prov->traceput as soon as the provider was removed from provider_table, before vf_provider_close() and before the loop that waits for providers_used() to drop to zero. A reader already inside vf_get()/vf_put() (or vfm_dump_one()) can have loaded the pointer before it was cleared, and vftrace_info_in() then spin_lock_irqsave()s inside freed slab memory and writes to it from the vsync IRQ path. Only reachable with vfm_trace_enable set via sysfs (default 0); otherwise both pointers are NULL. Detach the pointers where the table slot is cleared and free them after the drain loop. If that loop times out after 10000 iterations a reader is still running, so return without freeing and leak ~2.5 KiB of debug buffers rather than risk the use-after-free. vftrace_alloc_trace() also takes vfm_trace_num straight from sysfs with no bounds check and never checked the allocation before memset()ing it. Reject max <= 0, clamp to VFTRACE_MAX_NUM (4096 entries), use kzalloc() and return NULL on failure; every caller already treats NULL as tracing off. The clamp is silent - sysfs still reads back the written value. Generic VFM code, not SoC-gated. Not run on S905X5 hardware, and the race has not been reproduced on g12b.
- 2117Cm2: bound color index against the selected color table
The AMVECM_IOC_S_CMS_LUMA / _SAT / _HUE / _HUE_HS handlers take the colour index from cm_9_color_md or cm_14_color_md depending on color_type, but bound both with "cm_color <= cm_14_ecm2colormode_max - 1". With the 9-colour table an index of 9..13 passes and cm2_luma()/sat()/hue()/hue_by_hs() hand it to color_adj() with the nine-entry color_key_pts[], reading up to five entries past the array into the adjacent color_start[]/color_end[] statics. The four cm2 debug sysfs stores are worse: color_mode comes from kstrtoul() unbounded and is used directly as cm2_*_array[color_mode][0] = val, a write past a [cm_14_ecm2colormode_max][3] array (root only, as CoreELEC runs). Record the limit matching the selected table and compare against it in the ioctl handlers; reject an out-of-range colour mode in the sysfs stores via the existing kfree_buf path. color_adj() itself is unchanged. Not exercised on hardware.
- 2118Meson: s6: add audio_sdm_trim for the HIFI PLLs
The S6 counterpart of the g12a knob in 1050 and the S7D one in 2084, with the same contract: whole steps on /sys/module/amlogic_clk_soc_s6/parameters/audio_sdm_trim, positive is faster, about 15ppm a step, at most forty either way. mesons6_audio.dtsi clocks spdif and tdm from CLKID_HIFI_PLL and CLKID_HIFI1_PLL as the S7D one does, so the rate is moved through clk_set_rate(), which rewrites only frac while m, n and od stay put; a step that would reselect a table entry is refused rather than relocked under a running stream. The S6 PLLs also carry CLK_MESON_PLL_ROUND_CLOSEST, so hifi_trim_frac_only() refuses a target that another table entry is as near to, on top of the round-down rule. Neither PLL is audio-only (both appear in the vclk, vdec, hcodec, vpu, vapb, sd_emmc and gen parent lists here), so a trim is refused with -EBUSY while any clock in this provider is parented on the PLL. Clearing a trim is always allowed. Not yet exercised on hardware.
Kernel media drivers 21
- 2000Fix amstream_mutex imbalance on copy_to_user failure
Three ioctl error paths mis-account amstream_mutex when copy_to_user() faults. In amstream_do_ioctl_new(), GET_QOSINFO and GET_MVDECINFO take the mutex once and drop it once after kfree(tmpbuf); the copy loops over uarg_old->minfo[m] and uarg->vframe_qos[i] unlock and break, but that break only leaves the for loop, so the common unlock runs too and the mutex is released twice. In amstream_do_ioctl_old(), the UD_BUF_READ copy_to_user() failure path breaks out of the switch still holding it. Drop the two unlocks inside the copy loops, and add the missing unlock before the UD_BUF_READ break, matching the vdec_read_user_data() failure path above it. Triggering any of these needs a destination buffer that faults on write, so a broken or hostile local client rather than normal playback; not exercised on hardware.
- 2001Parse_sei: bound SEI parsing and CC copy
parse_sei() walks the SEI records the ucode collected into the picture's aux buffer, and three bounds are missing. The closed-caption copy writes ALIGN(payload_size, 8) bytes at hevc->sei_itu_data_buf + sei_itu_data_len without comparing against SEI_ITU_DATA_SIZE (5*1024), the size of the kmalloc. sei_itu_data_len is reset only once per picture, so writes accumulate over every record; a crafted picture packed with 9-byte CC payloads writes roughly 8.9 KB into the 5120-byte object and corrupts the slab. The two "while (*p == 0xff)" runs for payload_type and payload_size are unbounded and can walk p off the end of the record. And the fixed-size bodies read 24 bytes (mastering display), 4 (content light level) and 8 (T.35, for check_dvb_dv()) regardless of payload_size. The fix keeps one end pointer for the 0xff scans, requires payload_size to cover each fixed body, and skips a CC payload that would overflow the buffer. Each skip breaks out of the switch, so p still advances and the remaining messages are parsed. Well-formed streams are unaffected: real mastering display SEI is exactly 24 bytes, CLL 4, every recognised T.35 payload longer than 8, and per-picture CC is on the order of 100 bytes. The identical parse_sei() in h265_fb/vh265_fb.c is left alone; it is probed only for S5/T3X. All three defects need a malformed stream to trigger, so playback testing does not exercise them.
- 2002Bound aux data writes and reset aux sizes on buffer reuse
set_aux_data() repacks the firmware aux ring into pic->aux_data_buf or hw->dv_data_buf without checking the destination size and never clamps aux_count against the aux region; the h265 copy has both guards. Four input words can become twelve output bytes, so a malformed aux image can write past the 16 KiB dv_data_buf or the 24 KiB SEI_BUF_SIZE picture buffer from the decoder ISR thread (replay reaches offsets 40850 and 49126). Pass the destination capacity in, skip the repack when aux_count exceeds aux_size >> 1 or under twelve bytes remain, and check the space left before each record header and each four-byte group, dropping a record that does not fit. Also clear pic->aux_data_size and pic->hdr10p_data_size in get_free_fb() under the pool lock as v4l_get_free_fb() does, or a recycled buffer keeps the previous frame's T.35 record and parse_metadata() returns its stale Dolby Vision RPU. copy_dv_data() gains a SEI_BUF_SIZE check, since prefix_aux_buf_size is a writable module parameter. hw->dv_data_size is still not cleared for non-shown frames. Not exercised on hardware; g12b never runs this decoder.
- 2004Bound set_aux_data writes by the real aux buffer size
set_aux_data() bounds its output with AUX_DATA_SIZE1 (24 KB), but for hevc->pic_transfer the destination is only prefix_aux_size bytes: hevc_local_init() does vzalloc(prefix_aux_size) and prefix_aux_buf_size defaults to 12 KB. Output exceeds input - each record costs 8 header bytes plus one byte per 16-bit aux word - so an access unit of many tiny prefix SEI NALs, each its own tagged four-word group, yields ~3 bytes per input word. With the firmware prefix region capped at 6144 words, writes can reach 18432 bytes into a 12288-byte buffer. Triggering it needs crafted content. Compute the real capacity once (prefix_aux_size for pic_transfer, else AUX_DATA_SIZE1, which vdec_data_get_index() guarantees for cur_pic), use it in the entry check, and replace the AUX_DATA_SIZE1 - 11 guard with an end pointer tested before each write that advances p. aux_count is in 16-bit words and aux_size in bytes, so compare against aux_size / 2. Truncation mid-record drops the pending record instead of committing it. The same mismatch exists in the h265 v4l and fb copies; only decoder/h265/vh265.c is fixed here. Not exercised on S905X5 hardware.
- 2005Grow aux buffer before writing SL-HDR SEI
parse_one_sei_record() writes an 8-byte header plus the payload of an SL-HDR T.35 SEI (B5 00 3A 01 / B5 00 3A 00) to pic->aux_data_buf + pic->aux_data_size without checking that the buffer exists or how big it is. On the H264_SLICE_HEAD_DONE path no AUX_DATA_READY has run for that picture yet, so aux_data_buf is normally NULL and this is a NULL write that oopses the decoder IRQ thread; if a buffer from an earlier set_aux_data() is still attached, the write runs past an exact-size allocation instead. The existing aux_data_size + i >= SEI_ITU_DATA_SIZE guard compares against a 5K constant unrelated to the allocation. Compute the 8-aligned record length, refuse a record larger than SEI_ITU_DATA_SIZE, krealloc() the buffer (GFP_KERNEL, as set_aux_data() does; the call site sleeps under hw->pic_mutex) and only then write. Also require payload_size >= 5 / >= 4 before the signature compares, which otherwise read past the 8K sei_data_buf on a short tail payload. pic->aux_data_size is no longer clamped to SEI_ITU_DATA_SIZE; only each record is capped. Needs a stream carrying an SL-HDR T.35 SEI to hit; reviewed but not exercised on hardware.
- 2006Stop on INVALID_IDX in error-resilient ref refresh
In read_uncompressed_header(), the error-resilient reference refresh calls get_free_frame_buffer() and, on INVALID_IDX, calls aom_internal_error() and indexes anyway. Upstream libaom longjmps out of that call; here the longjmp is under #ifdef ORI_CODE (av1_bufmgr.c:717), so it returns and buf = &frame_bufs[-1] (INVALID_IDX is -1, av1_global.h:912) points just before BufferPool_s.frame_bufs, inside the enclosing AV1HW_s. It is written through and stored in cm->ref_frame_map[]: a bitstream-triggered OOB write. It needs real pool exhaustion - all 16 FRAME_BUFFERS held by ref_count or vf_ref - while this loop can need eight new buffers in one header. Clear the slot and continue instead; the old reference was already dropped, and NULL ref_frame_map entries are handled elsewhere. Needs the companion film-grain NULL-check patch. Untested: g12b has no AV1 decode.
- 2007Reject slices whose active ref count exceeds MAX_NUM_REF
constructRefPicList() fills m_apcRefPicList[list][ii] and m_bIsUsedAsLongTerm[list][ii] for every ii < m_aiNumRefIdx[list]. Both arrays are [NUM_REF_PIC_LIST_01][MAX_NUM_REF + 1], i.e. 17 entries per list. m_aiNumRefIdx[] comes from the ucode-written NumRefIdx param via two 6-bit masks, so it can be 0..63; the only bound applied is getNumRefEntries(), itself checked only against MAX_NUM_REF_PICS (29). A malformed or crafted VVC stream with 18 or more RPL entries and a matching num_ref_idx_active therefore writes indices 17..28, spilling L0 into row 1 and L1 past the arrays into m_aiRefPOCList, m_iDepth and m_scaledRefPicList. setRefPOCList() then dereferences a slot holding two packed POCs as a Picture *. The loops in vh266.c already cap with "&& i < MAX_NUM_REF"; h266_bufmgr.c does not. Reject the slice in xDecodeSlice() once m_aiNumRefIdx[] is filled, before anything consumes it, using the same "return -1" path as the two MAX_NUM_REF_PICS rejects just above. The spec limits num_ref_idx_active_minus1 to 0..14, so conforming streams are unaffected. Not exercised on hardware: no S905X5 here and no VVC stream decoded. If the closed ucode already clamps NumRefIdx, the check is inert.
- 2008Bound slice segment index to SLICE_MAX_NUM
Picture.slices[] is a fixed array of SLICE_MAX_NUM (= 1000) Slice pointers, but m_uiSliceSegmentIdx is never bounded against it, and the upstream VTM CHECK() guards are inert here (CHECK(a,b) expands to nothing). A malformed stream with more than 1000 slice segments in one picture walks the index past the array: index 1000 aliases the AML-added buf_cfg field, and beyond that the accesses leave the malloc(sizeof(Picture)) allocation - a stream-driven out-of-bounds kernel heap read and write. xDecodeSlice() now returns -1 on the non-first-slice path once the index has reached SLICE_MAX_NUM, ahead of every unguarded slices[] access; 0..999 still pass, the most slices any VVC level permits. It stops the overflow but does not reset the index, so such a stream keeps failing slices until the instance is torn down. Only S7D (S905X5) has VVC hardware, so this is inert on g12b; not exercised on S905X5 hardware - none available here.
- 2009Return -ENODEV once a failed video port frees the vdec
video_port_init()'s error path frees the vdec and sets priv->vdec = NULL, but the fd stays open. port_get_inited() then reports the video port "not inited" precisely because priv->vdec is NULL, so the next write() re-runs amstream_port_init(), passes the NULL vdec to vdec_resource_checking() and oopses; ~40 ioctl sites dereference priv->vdec unchecked too. vdec_init() fails for ordinary reasons (CMA allocation at 4K, probe -ENODEV, is_res_locked() -EBUSY), so this is a plain retry-after-failure oops; error3 reaches the same state via video_port_release(). Refuse the fd instead: port_vdec_gone() is true when a PORT_TYPE_VIDEO port has no vdec (open() fails if vdec_create() does, so only an init failure can clear it), and the write() re-init plus both ioctl entries return -ENODEV. The gate is blanket - vdec-independent ioctls on that fd get -ENODEV too. priv->vdec is still read unlocked, so a thread racing on a shared fd can pass the guard; not addressed here. Not exercised on hardware - reaching the state needs a forced vdec_init() failure.
- 2010Pair fetchbuf init/release with the use count
esparser_init() calls stbuf_fetch_init() only when esparser_use_count goes 0 -> 1, but esparser_release() called stbuf_fetch_release() on every release, outside the atomic_dec_and_test() block. With two concurrent amstream ES users (a video ES port plus an ES audio or subtitle port) the first close drops fetchbuf_cnt 1 -> 0, freeing the 64K fetchbuf and NULLing it while the other user is still writing; esparser_stbuf_write() reads the global fetchbuf without esparser_mutex, so it then fails with -EFAULT or writes into freed pages, and fetchbuf_cnt goes negative. Conversely an ES subtitle release returns before the old call site, so a subtitle-only session never freed the buffer. Move stbuf_fetch_release() into the atomic_dec_and_test() block beside the other last-user teardown, and in esparser_init() set first_use only after stbuf_fetch_init() succeeds, releasing on Err_1/Err_2 when it is set, so a failed init no longer leaks the reference. Not fixed here: esparser_stbuf_write() still touches the global fetchbuf without esparser_mutex, so a write racing the final close of its own stream can still see a freed pointer. Reached only with a second concurrent amstream ES user, which Kodi never opens; code-reviewed and built, not exercised on hardware.
- 2011Serialise vdec_reset against frame writes
AMSTREAM_IOC_VDEC_RESET and write() on the same /dev/amstream_vframe fd run with no lock between them. vdec_input_add_chunk caches a block and fills it via vframe_chunk_fill without input->lock, taking the lock only to publish the chunk. A racing vdec_reset runs vdec_input_release(), freeing every block (coherent DMA free plus kfree), then vdec_input_init(). A writer caught in between copies user data into a freed DMA buffer and publishes a chunk pointing at it, and the decoder programs a freed physical address. Needs a race with a local privileged client. Take the existing per-fd priv->mutex on both sides - the lock the VDECSTAT/VDECINFO ioctl cases already use here. It is dropped before the msleep(20) retry, and nothing below either call takes priv->mutex or amstream_mutex, so the amstream_port_init nesting is not inverted. The decoder half of the race is left for a separate patch. Not exercised on hardware: nothing CoreELEC ships opens /dev/amstream_vframe.
- 2012Cancel work before freeing its buffers on teardown
The AV1 multi-instance decoder can be torn down while its own work item is still running, and the teardown frees the buffers that work touches before it stops it. The AOM_AV1_RESULT_NEED_MORE_BUFFER branch of av1_work_implement() re-queues itself while get_free_buf_count() is 0 and never checks vdec->next_status, so a starved instance stays VDEC_STATUS_ACTIVE and vdec_disconnect() burns its full 2 s wait. Then vmav1_stop() calls av1_local_uninit() - freeing the rpm, lmem, aux and film-grain buffers and NULLing rpm_ptr/lmem_ptr - before cancel_work_sync(), and ammvdec_av1_remove() vfree()s hw->dv_data_buf before calling vmav1_stop(). If a buffer frees up in that window the still-queued work runs av1_release_bufs()/av1_continue_decoding() on those pointers: a use-after-free or NULL deref at stop. The fix leaves the NEED_MORE_BUFFER loop via DEC_RESULT_FORCE_EXIT when next_status is VDEC_STATUS_DISCONNECTED, cancels both work items before av1_local_uninit(), and frees dv_data_buf after vmav1_stop(). vav1_fb and the v4l copies carry the same two defects and are left alone. S922X has no AV1 hardware, so this is untested at runtime; S905X5 is the live target.
- 2013NULL-check film grain reference frame before use
config_film_grain_reg() takes the film grain reference slot index from HEVC_FG_STATUS bits 10:8 (0-7, REF_FRAMES is 8), fetches buf = cm->ref_frame_map[idx] and reads buf->film_grain_reg_valid. The only guard is a loop checking the index appears in cm->remapped_ref_idx[], which says nothing about the slot being populated: ref_frame_map[] entries are NULL until a frame refreshes them, reset_ref_frame_map() clears all eight on resync, and the port's aom_internal_error() does not longjmp, so the "nonexistent reference" path only logs and still stores remapped_ref_idx[i]. A stream joined mid-sequence can reach the read with buf == NULL, and the function runs in the hard-IRQ handler, so that is an oops in interrupt context. Fold the NULL test into the existing validity check so such a request takes the early return the "register data invalid" case already takes. Applied identically to all five AV1 front ends: decoder/vav1, decoder/vav1_fb, decoder_v4l/vav1, decoder_v4l/vav1_fb, decoder_v4l/vav1_t5d. In the _fb variants front_back_mode == 1 skips the remapped_ref_idx loop entirely, so this is the only check there. Deliberately left alone: the unchecked pic->cur_frame dereference earlier in the _fb copies. Not exercised on hardware - g12b has no AV1 hardware decode and never enters this path; whether the ucode raises an FGS request for an empty reference slot cannot be determined from this tree.
- 2014Config_mc_buffer: use L0 list in L0 error path
In config_mc_buffer() the REF_PIC_LIST_0 loop NULL-checks slice->m_apcRefPicList[REF_PIC_LIST_0][i], then looks the picture up with get_ref_pic_by_POC(). On failure it logs "%dth poc (%d) of RPS is not in the pic list0" but reads the POC out of REF_PIC_LIST_1[i] - a copy/paste from the list-1 loop below. That entry was never NULL-checked, and list 1 is only populated up to m_aiNumRefIdx[1] (Slices come zeroed from new_slice()), so on low-delay I/P streams it is NULL for every i. hevc_print() is a plain variadic function and flag 0 prints unconditionally, so the argument is always evaluated. A malformed or mid-GOP-joined stream that leaves a referenced picture with buf_cfg == NULL therefore turns a decode error into a NULL dereference in the IRQ thread instead of an error_mark. Read the POC from list 0 instead; the decoder_v4l copy carries identical code and gets the same change. The underlying condition (referenced picture with no buf_cfg) is left alone. Not exercised on hardware: VVC decode exists only on S905X5/S7D and no such box is available here.
- 2015Finish the disconnect under the core lock to avoid a UAF
vdec_core_thread() moved a vdec whose status is CONNECTED and whose next_status is DISCONNECTED off core->connected_vdec_list into a stack-local disconnecting_list, dropped vdec_core->lock and vdec_mutex, then ran another instance before finally doing list_del(), vdec_set_status() and complete(&vdec->inactive_done) at the end of the same loop iteration. In between the vdec is on no global list: vdec_disconnect() waits only 2000 ms on inactive_done and returns 0 on timeout, and vdec_connect_list_force_clear() scans only connected_vdec_list, so vdec_release() can vfree() the structure while the core thread still holds the pointer and writes to it three times. Do the three operations in place, still under vdec_core->lock and vdec_mutex, so a vdec is always either on connected_vdec_list or fully disconnected with inactive_done signalled. vdec_set_status() is a plain store and complete() takes only the completion's own leaf lock, so both are safe under spin_lock_irqsave(). Triggering the old race needs the core thread stalled for over two seconds in another instance's prepare/run (firmware or TEE load, slow CMA) plus a second connected instance to elect - PiP or overlapping instances. The separate case of a vdec stuck ACTIVE (the usual "vdec_disconnect timeout!!!" log line) is unchanged; force_clear() still recovers it. Not exercised on hardware; reviewed by code reading, applies at zero fuzz.
- 2016Fix box_dump length accounting and lock box during dump
decoder_mmu_box_dump_all() counts each per-box header twice: BUFPRINT already adds the header length to tsize and advances pbuf, then the call site does "s += decoder_mmu_box_dump(...)" and adds it again. The output cursor skips unwritten bytes and the total grows about twice as fast as the text; with several MMU boxes active it passes PAGE_SIZE, snprintf() gets a negative size that widens to a huge size_t, vsnprintf() hits WARN_ON_ONCE(size > INT_MAX) and returns 0, and box_dump_show() reports more than a page. Use "s =" as the bmmu sibling already does, and scnprintf() in all four BUFPRINT macros so the accumulated length is what was written. Both dump_all() functions walk box->sc_list / box->mm_list under only mgr->mutex, while every writer of those lists serialises on box->mutex and frees expansion nodes there, so a dump racing a decoder freeing buffers can follow a kfree()d node. Take box->mutex around the per-box dump. New nesting is mgr->mutex then box->mutex; no path takes them the other way (free paths drop box->mutex before mgr_del_box()). Both entry points are process context. Not exercised on hardware. Deliberately not fixed: the buf==NULL pr_info path still accumulates tsize while resetting pbuf each iteration.
- 2017Propagate copy_from_user failures in vframe_chunk_fill
vframe_chunk_fill() dropped the return value of copy_from_user_to_phyaddr() in all three branches and returned 0 unconditionally, so the "vframe_chunk_fill failed" check in vdec_input_add_chunk() could never fire on -EFAULT (aml_copy_from_user()) or -1 (codec_mm_vmap() NULL). A frame-mode write() whose user buffer loses a page after access_ok() then queues a chunk of size = count over a block tail still holding the previous frame, advances total_wr_count and reports full success. Capture and return the value at each call site, making the caller's existing cleanup reachable; add the missing kfree(chunk->head_meta_buf) there, as in vdec_input_release_chunk(). No lock is held at the early returns and block->wp is only advanced later in vframe_block_add_chunk(), so a failed fill leaves the block untouched. Not exercised on hardware.
- 2018Guard vdata and vdec_data_index before reading HDR10+ aux data
vvp9_event_cb() handles VFRAME_EVENT_RECEIVER_GET_AUX_DATA by reading vdec->vdata->data[pic->vdec_data_index].hdr10p_buf_size and .hdr10p_data_buf without checking either. vdec->vdata stays NULL when vdec_data_get() finds all VDEC_DATA_MAX_INSTANCE_NUM slots taken and on the v4l path, and init_pic_list()/config_pic() leave vdec_data_index at -1 (and aux_data_buf NULL) whenever the slot allocation failed. The HDR10+ and Dolby Vision paths send this event during ordinary VP9 playback, so a failed allocation means a NULL deref or a data[-1] read under lock_buffer_pool(). Gate both the event handler and the HDR10+ branch of set_frame_info() on vdata being non-NULL and vdec_data_index >= 0, and gate the SEI copy on a non-NULL source and aux_data_buf; size starts at 0 so the degraded case reports no aux data, which the receivers already handle. When allocation succeeded - the normal case - behaviour is unchanged. Needs a failed vdata slot allocation to trigger; not exercised on hardware. The same pattern in the front-back decoder (vp9_fb/vvp9_fb.c) is left alone.
- 2019Fix pic_transfer allocation error paths
Two allocation error paths around struct PIC_s are wrong. pic_alloc() jumps to the common "error:" label when the first aml_media_mem_alloc(sizeof(struct PIC_s)) returns NULL. That label calls pic_free(pic), which unconditionally reads pic->m_aiRefPOCList0/1 and m_aiRefPOCListData, so a failed kzalloc oopses instead of returning an error. Return NULL directly; the later failures are already safe. hevc_local_init() frees hevc->pic_transfer with vfree() when the aux_data_buf vzalloc fails, but pic_transfer came from kzalloc (struct PIC_s is well under SZ_8K), and the three m_aiRefPOC* lists are leaked with the field left dangling. Use pic_free() and clear the field. On 5.15 the vfree() only WARNs and returns, so the bug today is a splat plus that leak, not a use-after-free. Both paths need a GFP_KERNEL failure during decoder init or reset; neither has been exercised.
- 2020Clamp user_data_read to the user buffer and handle ring wrap
In vh265_user_data_read(), the branch for a record larger than the caller's buffer kept data_size = rec->rec_len instead of puserdata_para->buf_len, and did one flat copy_to_user() with no split at the end of the ring. buf_len and pbuf_addr arrive unvalidated from userspace via AMSTREAM_IOC_UD_BUF_READ, and records do wrap the 8 KB sei_user_data_buffer ring, so a small-buf_len read of a wrapped record copied up to 8 KB from inside the ring past its end: kernel heap disclosed to userspace, or usercopy_abort() with HARDENED_USERCOPY. Clamp data_size to buf_len and split the copy at data_buf_end, as vmpeg12_multi.c already does; reduce rec_start modulo the ring length so it cannot walk past the end across partial reads. Readers with a buffer smaller than a record must now loop. Untested on hardware: Kodi's AML codec never issues this ioctl. h265_fb and h266 carry the same defect, untouched here.
- 2021Copy profile names, list each slot once
vcodec_profile_register_v2() stored the caller's string pointer in the shared driver_profile[] slot, so the name in /sys/class/amstream/vcodec_profile aliases rodata of whichever decoder module registered it; the decoder modules never unregister on exit, so after rmmod that pointer dangles. vcodec_profile_register() also appended the same slot to vcodec_profile[] once per registering module, so a slot shared by a decoder and its _fb counterpart was listed twice. Copy the name into a static PROFILE_NAME_LEN (32) buffer per slot, and skip a slot already present in the list. On an AM6B Plus the hevc_fb, vp9_fb, av1_fb and avs2_fb lines now appear once instead of twice. The last module to register a (vformat, is_v4l) slot still wins the name; that and the missing unregistration on rmmod need their own patches.
CoreELEC settings 6
- 1000Offer yacer builds and keep auto-update on them
Offer only the trains the box can actually move to, and read our own feed beside the upstream one so these builds are listed without using a custom-channel slot. Point the automatic check at that feed too - it asked update.coreelec.org, which does not know these builds and would answer with an official one. Both feeds are fetched under a timeout, and About says where to report what.
- 2001Oe.py: RLock and atomic config save
conf_lock in src/oe.py was a module-level bool, cleared only just before a successful return; load_config and save_config swallow all exceptions, so one failure leaves it True and every later settings call blocks in load_config's `while conf_lock: time.sleep(0.2)`. It was not a lock either - test and store are separate bytecodes, and BlueZ handlers and the GUI thread all write. save_config opened the real file 'w' without fsync, so an interrupted save leaves an unparseable oe_settings.xml that hangs the service on every boot. Use a threading.RLock via `with`, held across the read-modify-write in write_setting and remove_node. save_config writes .tmp, fsyncs, os.replace()s in the same dir. load_config moves a damaged or non-UTF-8 file to oe_settings.xml.bad and returns an empty document, so the box comes up on defaults (hostname, BT audio device revert) instead of hanging. Exercised in plain Python against the pinned tree (949f6237), not on hardware.
- 2002Time out download_file and reject truncated downloads
download_file() treats a short read as a finished download and has no socket timeout, so a dropped or stalled connection during the ~200 MB update tar is either reported as success or hangs the update thread. http.client's HTTPResponse.read(amt) returns b'' rather than raising when the peer closes early, so a clean early close yields a truncated file that do_autoupdate moves into /storage/.update/ and offers a reboot for; the box then fails the image checks at boot. Content-Length was only passed to ProgressDialog.setSize(), never compared. urlopen() had no timeout and no default socket timeout is set, so a stall without a FIN blocks inside read(); the iscanceled()/abortRequested() checks only run between reads. Count the bytes written, compare with Content-Length after the cancel/abort check, and delete the destination and return None on a mismatch; pass timeout=30 to urlopen. On the error path close the dialog, response and file and remove the partial destination, each step guarded separately. Both new failure paths return None, which the caller already treats as "download failed". do_autoupdate still leaves update_in_progress set if the post- download move fails; not addressed here. Not exercised on hardware.
- 2003Serialise manual and auto update downloads
The background update thread and the settings configuration window run in the same service process and drive the same updates instance, and both download into the one hard-coded temp path oe.TEMP + 'update_file'. With AutoUpdate=auto, check_updates_v2 sets update_in_progress and calls do_autoupdate, which streams the tar for minutes; do_manual_update never checked that flag, overwrote self.update_file and called do_autoupdate again, whose download_file reopens the same temp file with mode 'wb'. The first download is truncated, the destination name can come from the other thread's URL, and the second shutil.move raises FileNotFoundError into a swallowing except: wasted download, a half-written file in .update, an update that silently never happens. Not a bad flash - busybox init checks the KERNEL and SYSTEM md5 sums before applying. The URL is no longer instance state: do_manual_update and check_updates_v2 pass it to do_autoupdate as a new url argument, which derives the destination name from it, and self.update_file is gone. A threading.Lock wraps makedirs/download/move/sync, acquired non-blocking so the window is never frozen behind a download it cannot start - a manual attempt during a background download logs and, when not silent, says so. The reboot prompt moved out from under the lock; the delattr of update_in_progress on a failed download gained a hasattr guard. The temp path is still shared and fixed; this serialises access rather than switching to tempfile.mkstemp. Pure Python, no SoC-specific code. Not exercised on hardware: the two-thread timing is the part only a real box can show.
- 2004Release the Bluetooth busy counter in finally
start_discovery, stop_discovery, trust_device and remove_device release oe.set_busy(0) with a plain statement after their D-Bus call, and @log.log_function() swallows the exception, so one DBusError leaves oe.__busy__ stuck at 1 for the life of the process. menu_connections() starts with "if oe.is_busy(): return", so the Bluetooth page then stops listing devices or starting discovery until Kodi restarts. Triggers: Remove on a device BlueZ already dropped (org.bluez.Error.DoesNotExist), or stop_discovery after bluetoothd restarted. Wrap the four bodies in try/finally, and drop set_busy(0) from dbus_error_handler - its three callers already release in their own finally, and the double release was driving the counter to -1. oe.set_busy itself is unchanged. Verified by code reading and py_compile; not exercised against a real D-Bus failure.
- 2005Do not join pinkey timer from its own thread
open_pinkey_window() starts a pinkeyTimer thread (60 s for a passkey, 30 s for a PIN code). On expiry pinkeyTimer.run() calls close_pinkey_window() on the timer's own thread, which does pinkey_timer.stop() then pinkey_timer.join() - joining itself, so join() raises RuntimeError('cannot join current thread'). @log.log_function swallows it, so pinkey_window.close() never runs and pinkey_timer and pinkey_window stay set: the passkey window is left on screen and the next pairing attempt reuses it, showing the old passkey. It clears only if BlueZ follows up with Agent.Cancel, InterfacesAdded, or display_pincode. Skip the join when the timer is joining itself; every other caller runs on a different thread and joins exactly as before. Also drop the redundant self._stop_event.clear() at the top of pinkeyTimer.run() - the event is already clear, and clearing it there erases a stop() that lands between start() and run(), leaving the caller blocked in join() for the full runtime. The same line in discoveryThread.run() is left alone: its stop() also calls parent.stop_discovery(), so it is not a no-op. Verified only by applying the patches-yacer stack and byte-compiling bluetooth.py; not exercised on real pairing hardware.
gpu-aml 8
- 2000Mali bifrost r44p0: fix CVE-2023-5427 event_queue UAF
kbase_poll() on /dev/mali0 calls poll_wait() with kctx->event_queue and then drops the file's fops_count, but the wait entry stays queued for the rest of the poll. kbase_flush() runs on every close() of the fd and, when map_count and fops_count are both zero, calls kbase_file_destroy_kctx(), which vfree()s kctx. If one thread blocks in poll() while another thread of the same process closes the fd, poll_freewait() (or ep_remove() at the final fput) then calls remove_wait_queue() on freed vmalloc memory, and a racing kbase_event_wakeup() writes into it. That is CVE-2023-5427, reachable by unprivileged userspace whenever a GLES/EGL event thread is not joined before close(). Move the wait queue into struct kbase_file, freed only from kbase_file_delete() on ->release, so it outlives every entry queued on it. kbase_event_wakeup() reaches it through kctx->kfile. One context per file, so the wake/wait pairing is unchanged. Taken from Arm's valhall/r44p0/cve_patches/CVE-2023-5427-GPUCORE-3998-GPUSWERRATA-1894-r45.diff (defect-test, build-metadata and kutf hunks dropped). valhall/r44p0 already has the fix, so only mali_kbase_bifrost.ko changes. No uapi, ioctl or sysfs change. Not compile-tested or booted here; wants a gpu-aml build and a g12b boot check. bifrost/r44p1 in the same repo is still unfixed but is not built.
- 2001Mali r44p0: fix CVE-2023-6241 (kbase_jit_grow race)
kbase_jit_grow() recorded old_size = reg->gpu_alloc->nents and delta = info->commit_pages - nents before the pool-grow loop, which drops the pool lock, mem_partials_lock and the VM lock around kbase_mem_pool_grow(). JIT regions carry BASE_MEM_GROW_ON_GPF, so a concurrent GPU page fault on the same region can grow nents in that window. On resume the stale delta and old_size make kbase_mem_grow_gpu_mapping() insert PTEs that no longer match the backing; the hole makes kbase_mem_shrink_gpu_mapping() miss part of the range and leaves the GPU mapping freed pages - CVE-2023-6241. Backport of ARM's GPUCORE-40571 in its bifrost/r49p1 form: after the locks are retaken, re-check nents >= commit_pages and take the existing "done" exit, else recompute old_size and delta. ">=" rather than the r46 diff's ">" keeps nents == commit_pages out of a zero-page allocation (-ENOMEM). Applied to both r44p0 trees gpu-aml builds, bifrost (Mali-G52, g12b) and valhall (Mali-G310, S7D); the fixed copies in-repo are only r44p1/r47p0/ r49p1, which this build does not use. A region shrinking during the window is still left to fail with -ENOMEM, as in ARM's code. Not exercised on hardware: the race is not reachable by ordinary playback.
- 2002Mali valhall r44p0: fix CVE-2025-0072 (CSF queue bind)
valhall/r44p0 csf/mali_kbase_csf.c never clears queue->user_io_addr when the user-io mapping is torn down, and kbase_csf_queue_bind() does not check it. A process with /dev/mali0 can bind queue Q to group G1 and mmap the cookie (allocating queue->phys and vmapping user_io_addr), terminate G1 without munmapping, then rebind Q to G2 and mmap again, overwriting phys and user_io_addr. Closing the first vma frees the *current* phys array, so userspace keeps a mapping of freed pages and the scheduler writes CS_INSERT/CS_EXTRACT through a dangling pointer. ARM's CVE-2025-0072. Backport of ARM's fix, taken verbatim from the r44p1 copy already in this tree (valhall/r44p1/cve_patches/CVE-2025-0072.diff): NULL user_io_addr after kernel_unmap_user_io_pages(), and reject a bind whose queue still has one. Only mali_kbase_valhall_csf.ko changes; g12b uses the bifrost JM module and is unaffected, so this is untested on hardware.
- 2003Wait for mali fault workers at JM context teardown
bifrost/r44p0, built as mali_kbase_bifrost.ko and loaded by the g12b Mali-G52, lacks Arm's CVE-2025-0427 fix on the job-manager path. kbase_mmu_interrupt() takes a reference on the faulting context and queues work on as->pf_wq; no teardown path waits for it, and kbasep_js_kctx_term() only WARN_ON()s a non-zero refcount. Teardown then frees the region tracker, the MMU context and the kctx itself while kbase_mmu_page_fault_worker() may still be running, so a process that provokes a page fault and closes /dev/mali0 at the same moment can race the worker into freed memory. Verbatim port of bifrost/r47p0/cve_patches/CVE-2025-0427_jm.diff, less the copyright bumps and the gpu_metrics_ctx_term() hunk (r44p0 has no such code). The valhall/r44p0 JM variant compiles the same file and is not covered here. Untested on hardware; applies with no fuzz.
- 2004Mali valhall r44p0: fix CVE-2025-0427 on CSF ctx term
valhall/r44p0 still has the pre-fix kbase_csf_ctx_term(): it flushes as->pf_wq once, but the address space stays published in kbdev->as_to_kctx until kbase_csf_scheduler_context_term() removes it at the end of teardown. A GPU/MMU fault handled after that flush still finds the outgoing context via kbase_ctx_sched_as_to_ctx_refcount(), takes a reference and queues work_pagefault/work_gpufault; nothing waits for it, so the worker dereferences the freed context. CVE-2025-0427/GPUCORE-45349. ARM's fix, backported from valhall/r44p1/cve_patches/CVE-2025-0427_csf.diff: loop flushing as->pf_wq while kctx->refcount keeps changing (locks dropped across each flush), then unassign the AS under mmu_hw_mutex+hwaccess_lock via the new kbase_ctx_sched_remove_ctx_nolock(), which kbase_ctx_sched_remove_ctx() now wraps. CSF is built only for CONFIG_MALI_CSF_SUPPORT=y (S905X5/S7D); g12b binds bifrost. Not exercised on hardware. The JM variant (mali_kbase_js.c) is not fixed here.
- 2005Mali valhall r44p0: fix CVE-2023-6363 KCPU queue UAF
kbase_csf_kcpu_queue_new() publishes the new queue into kctx->csf.kcpu_queues.in_use and .array[idx] before setup can still fail: the GFP_KERNEL kzalloc() of the dma-fence metadata and the WARN_ON() on snprintf() truncation of the timeline name. Both paths destroy_workqueue(queue->wq) + kfree(queue) and return an error without clearing in_use or array[idx], leaving a dangling pointer. Any later use of that id - enqueue ioctl, delete_queue(), or context_term() at process exit - is a use-after-free and double free. ARM fixed this as CVE-2023-6363; r44p0 as shipped by gpu-aml carries it unfixed. Move the bitmap_set() and array[idx] assignment past every point where setup can fail. Nothing in between reads in_use or array[], so success behaviour is unchanged. The remaining post-publish timer setup is safe because kcpu_queues.lock is held to function exit and every reader takes it. Only csf/ is touched, so the bifrost and valhall-jm modules g12b uses are unaffected. Not hardware-tested: no S905X5 board here.
- 2006Mali mpgpu: reject mpgpucmd writes with no argument
mpgpu_write() splits the written string with strsep(&cprt, " "). A write containing no space leaves cprt NULL, and every command except "preheat" then uses it unchecked: the mpl2 branch calls strlen(cprt), and the scmpp, bstgpu, bstpp and lmt branches pass NULL to kstrtouint(), which reads s[0]. Either way the kernel oopses in the sysfs write path. The node is 0644, so this needs root, but GPU tuning scripts write it. On valhall/r44p0 the commands are matched by prefix, so "echo mpl2 > /sys/class/mpgpu/mpgpucmd" suffices; bifrost/r44p0 compares MAX_TOKEN (20) bytes and needs echo -n. Add a branch after the preheat case - the only argument-less command - that goes to the existing quit label when cprt is NULL. Well-formed writes are unaffected; a rejected write still returns count, as the other quit paths do. Only the bifrost/r44p0 and valhall/r44p0 copies built by package.mk are patched; the other in-tree copies share the bug but are not compiled. Not exercised on hardware - the pre-patch reproducer is itself an oops.
- 2007Read domain_stat under hwaccess_lock when powered
domain_stat_read() in the Amlogic devicetree platform glue calls kbase_pm_get_ready_cores(), which reads the GPU SHADER_READY registers, without taking hwaccess_lock and without checking kbdev->pm.backend.gpu_powered. mali_get_online_pp() in platform_gx.c does both. Reading /sys/class/mpgpu/domain_stat (mode 0644) while the GPU is runtime-suspended - the normal state when nothing is composing - hits the WARN_ON(!gpu_powered) inside kbase_reg_read(): it returns 0, but prints a backtrace per read, so a loop over the node floods the log, and panics on a panic_on_warn kernel. The unlocked read also races kbase_pm_update_state. Take hwaccess_lock and skip the read when unpowered; core_ready starts at 0, so the node prints what it printed before in both power states. Patched in bifrost/r44p0 and valhall/r44p0, covering all three gpu-aml modules. Not built or hardware-tested here.