From: Lothar Rubusch <l.rubusch@gmail.com>
To: thorsten.blum@linux.dev, herbert@gondor.apana.org.au,
davem@davemloft.net, nicolas.ferre@microchip.com,
alexandre.belloni@bootlin.com, claudiu.beznea@tuxon.dev,
tudor.ambarus@linaro.org, ardb@kernel.org, linusw@kernel.org,
krzk+dt@kernel.org
Cc: linux-crypto@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, l.rubusch@gmail.com
Subject: [PATCH v4 02/12] crypto: atmel-ecc - fix multi-device kpp registration
Date: Fri, 22 May 2026 23:01:24 +0000 [thread overview]
Message-ID: <20260522230134.32414-3-l.rubusch@gmail.com> (raw)
In-Reply-To: <20260522230134.32414-1-l.rubusch@gmail.com>
When multiple atmel-ecc hardware accelerator chips are attached to the
same host, registering the same static kpp_alg structure multiple times
corrupts internal fields used by the crypto core's algorithm list. This
leads to immediate list corruption or kernel panics.
Additionally, removing an individual device via sysfs while active crypto
transformations (TFMs) are running triggers a use-after-free (UAF) bug.
Because the device driver core lacks unbind error-handling paths, the
underlying memory allocated via devm for the i2c_priv structure is freed
unconditionally, leaving active transformation context pointers dangling.
Fix these problems by implementing a centralized subsystem tracking matrix:
1. Introduce a global subsystem mutex and reference counter to ensure
that the static 'atmel_ecdh_nist_p256' structure is only registered by
the first probing device, and unregistered exclusively when the last
supporting device unbinds.
2. Maintain per-device allocation tracking with 'tfm_count'. On remove,
mark the device unready to halt load-balancing assignments, and block
via a completion barrier until all pending transformation contexts bound
to that specific physical hardware are freed.
3. Fix a critical re-registration race where a high-velocity unbind and
subsequent re-probe cycles occur while crypto core asynchronous users
are still purging. Establish a global 'atmel_ecc_unreg_active' state
fence to force concurrent probing threads to execute a 2-second timeout
bounded wait_for_completion_timeout() rather than unsafely mutating
static lists.
Fixes: 11105693fa05 ("crypto: atmel-ecc - introduce Microchip / Atmel ECC driver")
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 107 ++++++++++++++++++++++++++++---------
drivers/crypto/atmel-i2c.h | 1 +
2 files changed, 82 insertions(+), 26 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 94360d29f9f9..005a9a3d919c 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -23,6 +23,11 @@
#include <crypto/kpp.h>
#include "atmel-i2c.h"
+static DEFINE_MUTEX(atmel_ecc_kpp_lock);
+static int atmel_ecc_kpp_refcnt;
+DECLARE_COMPLETION(atmel_ecc_unreg_done);
+static bool atmel_ecc_unreg_active;
+
static struct atmel_ecc_driver_data driver_data;
/**
@@ -243,7 +248,8 @@ static void atmel_ecc_i2c_client_free(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
- atomic_dec(&i2c_priv->tfm_count);
+ if (atomic_dec_and_test(&i2c_priv->tfm_count))
+ complete(&i2c_priv->remove_done);
}
static int atmel_ecdh_init_tfm(struct crypto_kpp *tfm)
@@ -278,7 +284,8 @@ static void atmel_ecdh_exit_tfm(struct crypto_kpp *tfm)
struct atmel_ecdh_ctx *ctx = kpp_tfm_ctx(tfm);
kfree(ctx->public_key);
- crypto_free_kpp(ctx->fallback);
+ if (ctx->fallback)
+ crypto_free_kpp(ctx->fallback);
atmel_ecc_i2c_client_free(ctx->client);
}
@@ -317,6 +324,7 @@ static struct kpp_alg atmel_ecdh_nist_p256 = {
static int atmel_ecc_probe(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv;
+ unsigned long timeout;
int ret;
ret = atmel_i2c_probe(client);
@@ -332,50 +340,97 @@ static int atmel_ecc_probe(struct i2c_client *client)
i2c_priv->ready = true;
spin_unlock(&driver_data.i2c_list_lock);
- ret = crypto_register_kpp(&atmel_ecdh_nist_p256);
- if (ret) {
- spin_lock(&driver_data.i2c_list_lock);
- i2c_priv->ready = false;
- list_del(&i2c_priv->i2c_client_list_node);
- spin_unlock(&driver_data.i2c_list_lock);
+ mutex_lock(&atmel_ecc_kpp_lock);
+ /*
+ * For cases where the same/last such device is still in unregistering,
+ * and now re-registering (refcnt is 0, but completion still exists).
+ * Safely capture the pointer, drop the lock and sleep until it
+ * terminates upon completion or retry limit reached.
+ */
+ while (atmel_ecc_unreg_active) {
+ mutex_unlock(&atmel_ecc_kpp_lock);
+ timeout = wait_for_completion_timeout(&atmel_ecc_unreg_done,
+ msecs_to_jiffies(2000));
+ mutex_lock(&atmel_ecc_kpp_lock);
+
+ if (timeout == 0) {
+ spin_lock(&driver_data.i2c_list_lock);
+ i2c_priv->ready = false;
+ list_del(&i2c_priv->i2c_client_list_node);
+ spin_unlock(&driver_data.i2c_list_lock);
+ mutex_unlock(&atmel_ecc_kpp_lock);
+
+ dev_err(&client->dev, "probe timed out, former driver instance not fully deregistered\n");
+ return -ETIMEDOUT;
+ }
+ }
- dev_err(&client->dev, "%s alg registration failed\n",
- atmel_ecdh_nist_p256.base.cra_driver_name);
- return ret;
- } else {
- dev_info(&client->dev, "atmel ecc algorithms registered in /proc/crypto\n");
+ if (atmel_ecc_kpp_refcnt == 0) {
+ ret = crypto_register_kpp(&atmel_ecdh_nist_p256);
+ if (ret) {
+ spin_lock(&driver_data.i2c_list_lock);
+ i2c_priv->ready = false;
+ list_del(&i2c_priv->i2c_client_list_node);
+ spin_unlock(&driver_data.i2c_list_lock);
+ mutex_unlock(&atmel_ecc_kpp_lock);
+
+ dev_err(&client->dev, "%s alg registration failed\n",
+ atmel_ecdh_nist_p256.base.cra_driver_name);
+ return ret;
+ }
}
+ atmel_ecc_kpp_refcnt++;
+ mutex_unlock(&atmel_ecc_kpp_lock);
+ dev_info(&client->dev, "atmel ecc algorithms registered in /proc/crypto\n");
return ret;
}
static void atmel_ecc_remove(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ bool trigger_unreg = false;
spin_lock(&driver_data.i2c_list_lock);
i2c_priv->ready = false;
spin_unlock(&driver_data.i2c_list_lock);
- /* Return EBUSY if i2c client already allocated. */
- if (atomic_read(&i2c_priv->tfm_count)) {
- /*
- * After we return here, the memory backing the device is freed.
- * That happens no matter what the return value of this function
- * is because in the Linux device model there is no error
- * handling for unbinding a driver.
- * If there is still some action pending, it probably involves
- * accessing the freed memory.
- */
- dev_emerg(&client->dev, "Device is busy, expect memory corruption.\n");
- return;
+ /*
+ * The Linux crypto core automatically blocks until all active
+ * transformations utilizing that specific algorithm structure
+ * are fully freed and closed.
+ */
+ mutex_lock(&atmel_ecc_kpp_lock);
+ atmel_ecc_kpp_refcnt--;
+
+ if (atmel_ecc_kpp_refcnt == 0) {
+ trigger_unreg = true;
+ atmel_ecc_unreg_active = true;
+ reinit_completion(&atmel_ecc_unreg_done);
}
+ mutex_unlock(&atmel_ecc_kpp_lock);
- crypto_unregister_kpp(&atmel_ecdh_nist_p256);
+ if (atomic_read(&i2c_priv->tfm_count))
+ wait_for_completion(&i2c_priv->remove_done);
spin_lock(&driver_data.i2c_list_lock);
list_del(&i2c_priv->i2c_client_list_node);
spin_unlock(&driver_data.i2c_list_lock);
+
+ /*
+ * The driver registers once an algorithm, but maintains a list of
+ * supporting i2c devices. Unregister the algorithm only, when the last
+ * supporting device deregisters. Use completions to assure no inflight
+ * TFMs and/or re-registering driver probe will then loose memory
+ * by over initializing the global statics.
+ */
+ if (trigger_unreg) {
+ crypto_unregister_kpp(&atmel_ecdh_nist_p256);
+ mutex_lock(&atmel_ecc_kpp_lock);
+ atmel_ecc_unreg_active = false;
+ complete_all(&atmel_ecc_unreg_done);
+ mutex_unlock(&atmel_ecc_kpp_lock);
+ }
}
static const struct of_device_id atmel_ecc_dt_ids[] = {
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index e3b12030f9c4..b320559e50eb 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -146,6 +146,7 @@ struct atmel_i2c_client_priv {
size_t wake_token_sz;
atomic_t tfm_count ____cacheline_aligned;
struct hwrng hwrng;
+ struct completion remove_done;
bool ready;
};
--
2.39.5
next prev parent reply other threads:[~2026-05-22 23:01 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-22 23:01 [PATCH v4 00/12] crypto: atmel - introduce shared i2c core client management and capability-based selection framework Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 01/12] crypto: atmel-ecc - fix use after free situation Lothar Rubusch
2026-05-22 23:01 ` Lothar Rubusch [this message]
2026-05-22 23:01 ` [PATCH v4 03/12] crypto: atmel-sha204a - fix heap info leak on I2C transfer failure Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 04/12] crypto: atmel-ecc - rename driver_data before moving it into atmel-i2c Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 05/12] crypto: atmel - rename atmel_ecc_driver_data to atmel_i2c_client_mgmt Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 06/12] crypto: atmel-i2c - move client management instance into core Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 07/12] crypto: atmel-i2c - introduce shared teardown helpers and fix queue flush Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 08/12] crypto: atmel-ecc - switch to module_i2c_driver Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 09/12] crypto: atmel-i2c - move shared client allocation logic to core Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 10/12] crypto: atmel-i2c - implement capability-based client selection Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 11/12] crypto: atmel-sha204a - integrate into core management tracking Lothar Rubusch
2026-05-22 23:01 ` [PATCH v4 12/12] crypto: atmel-sha204a - switch to module_i2c_driver Lothar Rubusch
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260522230134.32414-3-l.rubusch@gmail.com \
--to=l.rubusch@gmail.com \
--cc=alexandre.belloni@bootlin.com \
--cc=ardb@kernel.org \
--cc=claudiu.beznea@tuxon.dev \
--cc=davem@davemloft.net \
--cc=herbert@gondor.apana.org.au \
--cc=krzk+dt@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nicolas.ferre@microchip.com \
--cc=thorsten.blum@linux.dev \
--cc=tudor.ambarus@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox