Skip to content

icvs: fix security and stability bugs in CVS driver#40

Open
vad1110 wants to merge 5 commits into
mainfrom
fix/security-and-stability-bugs
Open

icvs: fix security and stability bugs in CVS driver#40
vad1110 wants to merge 5 commits into
mainfrom
fix/security-and-stability-bugs

Conversation

@vad1110
Copy link
Copy Markdown
Contributor

@vad1110 vad1110 commented May 19, 2026

Summary

This series fixes 10 bugs found during a security and stability review of the Intel CVS i2c/platform driver. All fixes are self-contained and the module builds cleanly.

One known design issue (firmware loaded via user-supplied fd instead of request_firmware()) is intentionally deferred as it requires a co-change with the LVFS userspace plugin.

Commits

Commit Area Issues fixed
icvs: fix uninitialized spinlock and work queue objects Init ordering Spinlock buffer_lock, fw_dl_task work item, and three wait queue heads were only initialized inside the FULLCAP branch; LIGHTCAP sysfs paths hit uninitialized objects. Adds FULLCAP capability guard in sysfs handlers.
icvs: fix null ACPI handle dereference in probe Probe ACPI_HANDLE(dev) returns NULL on non-ACPI platforms; was passed unchecked to acpi_evaluate_integer().
icvs: fix memory leaks in I2C helpers and firmware buffer management Memory (a) cvs_read_i2c/cvs_write_i2c used devm_kzalloc for per-call buffers — leaked until device removal. Validation moved before allocation. (b) fw_buffer not freed on repeated pre_store writes. (c) fw_buffer not freed on kernel_read failure.
icvs: fix integer type safety in firmware download path Overflow (a) fw_buffer_size - FW_BIN_HDR_SIZE wraps to ~4 GB when image < 256 bytes, causing out-of-bounds vmalloc reads. (b) Last-packet fw_size -= I2C_PKT_SIZE underflows. (c) generic_file_llseek return (loff_t) stored in int, truncating files > 2 GiB and producing a wrong seek-back offset.
icvs: fix driver teardown and sysfs download state management Lifecycle (a) Global cvs pointer left dangling on probe failure and after removal. (b) cancel_work_sync() never called in remove — work item could run against freed struct. (c) Early return on reset failure in remove left fw_buffer unfreed and cvs dangling. (d) pre_show reset in-flight download state on every sysfs read.

Testing

  • Module builds cleanly with -Wall -Wextra -Werror
  • All fixes are defensive guards on error paths or corrections to incorrect types/initialization

Not addressed in this series

  • Firmware loaded via user-supplied fd (plugin_to_cvs_interface.fw_bin_fd): the correct fix is request_firmware() + firmware name via sysfs, but this requires a coordinated change with the LVFS userspace plugin. Tracked separately.

vad1110 added 5 commits May 19, 2026 10:02
buffer_lock spinlock, fw_dl_task work item and the three wait queue
heads (hostwake_event, update_complete_event, lvfs_fwdl_complete_event)
were only initialized inside the ICVS_FULLCAP branch.  On LIGHTCAP
devices the sysfs handlers cvs_ctrl_data_pre_{show,store} and
cvs_ctrl_data_fwupd_show are still reachable and use all of those
objects, leading to undefined behaviour on the uninitialized memory.

Move all initializations unconditionally to the top of cvs_common_probe
so every device variant starts with valid objects.  Remove the now-
redundant duplicate block from inside the FULLCAP branch.

Add an ICVS_FULLCAP capability guard in both sysfs handlers so LIGHTCAP
devices receive -EOPNOTSUPP rather than silently attempting FW update
operations that require hardware resources only present on FULLCAP.

Signed-off-by: Miguel Vadillo <miguel.vadillo@intel.com>
ACPI_HANDLE() returns NULL when the device has no ACPI companion node
(e.g. platform devices not enumerated via ACPI).  The return value was
passed directly to acpi_evaluate_integer() without a null check, causing
a null-pointer dereference.

Guard the find_oem_prod_id() call so it is only attempted when a valid
ACPI handle is available.  A missing OEM product ID is non-fatal; the
driver continues with oem_prod_id left at its zero-initialised value.

Signed-off-by: Miguel Vadillo <miguel.vadillo@intel.com>
Three related memory leak issues:

1. cvs_read_i2c() and cvs_write_i2c() used devm_kzalloc() for
   per-call temporary receive/transmit buffers.  devm_ allocations are
   only released at device removal, so every I2C transaction
   (including those triggered by repeated sysfs reads) accumulates
   allocations in the device-managed resource list without bound.
   Switch to kzalloc()/kfree() with explicit release on all paths.
   Move argument validation before allocation in cvs_read_i2c() to
   avoid allocating and immediately leaking when inputs are invalid.

2. cvs_ctrl_data_pre_store() allocates fw_buffer with vmalloc() but
   does not free the previous buffer when called a second time (e.g.
   LVFS retry).  Add an explicit vfree() + NULL reset before each
   new allocation.

3. On kernel_read() failure in cvs_ctrl_data_pre_store() the function
   returned -EIO while leaving fw_buffer allocated with partially
   written data.  Free and NULL the buffer so a subsequent write
   starts from a clean state.

Signed-off-by: Miguel Vadillo <miguel.vadillo@intel.com>
Two related issues:

1. fw_buffer_size is declared as u32.  In both cvs_dev_fw_dl_data() and
   cvs_fw_dl_thread() the expression fw_buffer_size - FW_BIN_HDR_SIZE
   is evaluated as unsigned arithmetic.  When the firmware image is
   smaller than FW_BIN_HDR_SIZE (256 bytes) the result wraps to a large
   positive value, causing a while loop that reads far beyond the vmalloc
   buffer.  Add an explicit size check before the subtraction in both
   call sites.

   Additionally, cvs_dev_fw_dl_data() advances fw_size by a fixed
   I2C_PKT_SIZE on every iteration.  The last packet may be shorter; the
   unsigned subtraction wraps and fw_buff_ptr overshoots the buffer.
   Replace the subtraction with min_t(u32, fw_size, I2C_PKT_SIZE).

2. cvs_ctrl_data_pre_store() stored the return value of
   generic_file_llseek() (loff_t / s64) in an int, silently truncating
   files larger than 2 GiB and producing a negative seek-back delta that
   would seek forward instead of rewinding.  Replace the int local with
   loff_t, switch to vfs_llseek(), and treat a non-positive result as an
   error rather than proceeding with a bogus size.

Signed-off-by: Miguel Vadillo <miguel.vadillo@intel.com>
Three related lifecycle and state-consistency issues:

1. The global 'cvs' pointer was assigned at the beginning of
   cvs_common_probe() before hardware initialisation succeeded.  On any
   failure path the struct is freed via devm_kfree() but cvs is left
   pointing at freed memory.  Set cvs = NULL in the failure branch of
   the exit label.

   In cvs_common_remove() the pointer was also never cleared after
   devm_kfree(), so any code running after remove (e.g. a concurrent
   sysfs access) would dereference freed memory.  Set cvs = NULL at
   the end of the remove path.

2. cancel_work_sync() was never called in cvs_common_remove().  The
   existing signal + wait_event_interruptible() sequence is only
   reached when fw_dl_task_started is true; if the work item had been
   scheduled but had not yet set that flag the work would run against
   the already-freed intel_cvs struct.  Add cancel_work_sync() after
   the signal/wait block to cover that race.

   Also remove the premature 'return' on cvs_reset_cv_device() failure
   that left fw_buffer unfreed and cvs dangling; log the error and
   continue cleanup instead.

3. cvs_ctrl_data_pre_show() reset info_fwupd, fw_dl_task_finished and
   fw_dl_task_started on every read.  Any concurrent sysfs read during
   an in-flight download zeroed the progress counters and caused the
   LVFS plugin to see a spurious completion or restart condition.
   Move the state reset to cvs_ctrl_data_pre_store() where it belongs:
   at the beginning of a new update request, not on every read.

Signed-off-by: Miguel Vadillo <miguel.vadillo@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant