Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Pavitrakumar Managutte" <pavitrakumarm@vayavyalabs.com>,
	devicetree@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v26 2/4] crypto: spacc - Add SPAcc ahash support
Date: Mon, 19 Oct 2026 17:03:30 +0000	[thread overview]
Message-ID: <sashiko-outbox-177985@kernel.org> (raw)
In-Reply-To: <20261019162457.1000115-3-pavitrakumarm@vayavyalabs.com>

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [High] crypto: spacc: double free of salg in spacc_probe_hashes() on error
- [High] crypto: spacc: BUG_ON() in spacc_sg_to_ddt() via sg_last() on partial sg
- [High] crypto: spacc: statesize overflow and broken BLOCK_ONLY in spacc_register_hash()
- [High] crypto: spacc: unconditionally setting CRYPTO_ALG_OPTIONAL_KEY and setkey()
- [High] crypto: spacc: unhandled CMD0 interrupt storm in spacc_process_irq()
- [High] crypto: spacc: use-after-free and kthread leak in spacc_crypto_remove()
- [High] crypto: spacc: premature dma_ref_cnt dec and missing wake in do_one_request()
- [Medium] crypto: spacc: spacc_job_request() treats -EINVAL from spacc_ctx_request() as valid
- [Medium] crypto: spacc: out-of-bounds read of version_string[] in spacc_init()
- [Medium] crypto: spacc: missing acquire barrier in spacc_compute_xcbc_key() wait
- [Medium] crypto: spacc: unlocked IRQ state updates and unmasked status in IRQ handler
- [Medium] crypto: spacc - copy digest only on success in spacc_digest_cb()
- [Low] crypto: spacc: missing closing brace in spacc_unregister_hash_algs()
--

commit 13f9355d3fad0b1de2c06505109dd33bc6804ae0
Author: Pavitrakumar Managutte <pavitrakumarm@vayavyalabs.com>

crypto: spacc - Add SPAcc ahash support

This commit adds core SPAcc driver support along with ahash algorithm
implementations for MD5, SHA1, SHA2, SHA3, SM4-CMAC, and SM4-XCBC.

> diff --git a/drivers/crypto/dwc-spacc/spacc_ahash.c b/drivers/crypto/dwc-spacc/spacc_ahash.c
> new file mode 100644
> index 0000000000000..76dd497a0a17a
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/spacc_ahash.c

[ ... ]

> +static int spacc_hash_init_dma(struct device *dev, struct ahash_request *req)
> +{
> +	int rc = -1;
> +	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
> +	struct spacc_crypto_ctx *tctx = crypto_ahash_ctx(tfm);
> +	struct spacc_crypto_reqctx *ctx = ahash_request_ctx(req);
> +	struct spacc_priv *priv = dev_get_drvdata(dev);
> +
> +	/*
> +	 * do_one_request runs in a sleepable context,
> +	 * so GFP_KERNEL is always safe here.
> +	 */
> +	gfp_t mflags = GFP_KERNEL;
> +
> +	ctx->acb.new_handle = -1;
> +	ctx->digest_buf = dma_pool_alloc(priv->hash_pool, mflags,
> +					 &ctx->digest_dma);

[ ... ]

> +static void spacc_digest_cb(void *spacc, void *tfm)
> +{
> +	int err;
> +	int dig_sz;
> +	struct ahash_cb_data *cb = tfm;
> +	struct spacc_device *device = spacc;
> +	struct spacc_priv *priv = container_of(device, struct spacc_priv,
> +					       spacc);
> +
> +	dig_sz = crypto_ahash_digestsize(crypto_ahash_reqtfm(cb->req));
> +
> +	if (cb->ctx->single_shot)
> +		memcpy(cb->req->result, cb->ctx->digest_buf, dig_sz);
> +	else
> +		memcpy(cb->tctx->digest_ctx_buf, cb->ctx->digest_buf, dig_sz);
> +
> +	err = cb->spacc->job[cb->new_handle].job_err;

[Severity: Medium]
Should spacc_digest_cb() check job_err before copying cb->ctx->digest_buf
into cb->req->result?

In spacc_hash_init_dma(), ctx->digest_buf is allocated from priv->hash_pool
via dma_pool_alloc() without __GFP_ZERO, so it contains dma_pool freelist
metadata and stale digest bytes until the hardware writes the result. If a
job fails in hardware or during deferred submission in spacc_process_jb()
with a non-zero job_err, copying digest_buf unconditionally overwrites
cb->req->result with uninitialized or stale kernel memory.

[ ... ]

> +static int spacc_hash_try_hw(struct spacc_crypto_ctx *tctx,
> +			     struct spacc_priv *priv,
> +			     const struct spacc_alg *salg,
> +			     struct crypto_ahash *reqtfm,
> +			     struct ahash_request *req)
> +{
> +	int rc;
> +
> +	guard(mutex)(&tctx->lock);
> +
> +	if (tctx->handle < 0 || !tctx->ctx_valid) {
> +		/*
> +		 * spacc_open() acquires ctx_sem which may sleep;
> +		 * do_one_request runs in the crypto_engine workqueue
> +		 * which is a sleepable context, so this is safe.
> +		 */
> +		tctx->handle = spacc_open(&priv->spacc,
> +					  CRYPTO_MODE_NULL, salg->mode->id,
> +					  -1, 0, spacc_digest_cb, reqtfm);
> +		if (tctx->handle < 0) {
> +			dev_dbg(salg->dev, "context open failed, fallback\n");
> +			return tctx->handle;
> +		}
> +
> +		rc = spacc_set_operation(&priv->spacc, tctx->handle,
> +				OP_ENCRYPT, ICV_HASH, IP_ICV_OFFSET, 0, 0, 0);
> +		if (rc < 0) {
> +			spacc_close(&priv->spacc, tctx->handle);
> +			tctx->handle = -1;
> +			return rc;
> +		}
> +
> +		if (tctx->keylen > 0) {
> +			u32 mode_id = salg->mode->id;

[ ... ]

> +/* Crypto engine hash operation */
> +static int spacc_hash_do_one_request(struct crypto_engine *engine, void *areq)
> +{

[ ... ]

> +	atomic_inc(&priv->dma_ref_cnt);
> +
> +	rc = spacc_packet_enqueue_ddt(&priv->spacc, ctx->acb.new_handle,
> +			&ctx->src, &ctx->dst, req->nbytes,
> +			0, req->nbytes, 0, 0, 0, false);
> +	if (rc < 0) {
> +
> +		atomic_dec(&priv->dma_ref_cnt);
> +		spacc_free_mems(ctx, tctx, req);
> +		dma_pool_free(priv->hash_pool, ctx->digest_buf,
> +				ctx->digest_dma);
> +		pdu_ddt_free(&ctx->dst);
> +
> +		if (ctx->acb.new_handle >= 0) {
> +			spacc_close(&priv->spacc, ctx->acb.new_handle);
> +			ctx->acb.new_handle = -1;
> +		}

[Severity: High]
In spacc_hash_do_one_request(), when spacc_packet_enqueue_ddt() fails,
priv->dma_ref_cnt is decremented before freeing the DMA buffers and closing
the job handle, and priv->drain_wq is not woken up.

If spacc_crypto_remove() is waiting on priv->drain_wq, could it either hang
indefinitely because wake_up(&priv->drain_wq) is never called when
dma_ref_cnt reaches zero, or wake up early and destroy priv->hash_pool, the
DDT pools, and spacc->job while this error path is still freeing them?

Should this error path release all resources first and then call
atomic_dec_and_test(&priv->dma_ref_cnt) followed by
wake_up(&priv->drain_wq), matching spacc_digest_cb()?

[ ... ]

> +static void spacc_hash_exit_tfm(struct crypto_ahash *tfm)
> +{
> +	struct spacc_crypto_ctx *tctx = crypto_ahash_ctx(tfm);
> +	struct spacc_priv *priv = dev_get_drvdata(tctx->dev);
> +
> +	if (priv && tctx->handle >= 0)
> +		spacc_close(&priv->spacc, tctx->handle);
> +
> +	put_device(tctx->dev);
> +	mutex_destroy(&tctx->lock);
> +}

[ ... ]

> +static int spacc_hash_digest(struct ahash_request *req)
> +{
> +	struct crypto_ahash *reqtfm = crypto_ahash_reqtfm(req);
> +	struct spacc_crypto_ctx *tctx = crypto_ahash_ctx(reqtfm);
> +	struct spacc_priv *priv = dev_get_drvdata(tctx->dev);
> +	unsigned long flags;
> +	bool removing;
> +
> +	if (!priv || !priv->engine)
> +		return -ENODEV;
> +
> +	spin_lock_irqsave(&priv->spacc.lock, flags);
> +	removing = priv->removing;
> +	spin_unlock_irqrestore(&priv->spacc.lock, flags);
> +
> +	if (removing)
> +		return -ENODEV;
> +
> +	return crypto_transfer_hash_request_to_engine(priv->engine, req);
> +}

[ ... ]

> +static void spacc_hash_alg_destroy(struct crypto_alg *alg)
> +{
> +	struct spacc_alg *salg = container_of(alg, struct spacc_alg,
> +					      alg.hash.base.halg.base);
> +
> +	kfree(salg);
> +}
> +
> +static const struct ahash_engine_alg spacc_hash_template = {
> +	.base = {
> +		.init		= spacc_hash_init,
> +		.update		= spacc_hash_update,
> +		.final		= spacc_hash_final,
> +		.finup		= spacc_hash_finup,
> +		.digest		= spacc_hash_digest,
> +		.setkey		= spacc_hash_setkey,
> +		.export		= spacc_hash_export,
> +		.import		= spacc_hash_import,
> +		.init_tfm	= spacc_hash_init_tfm,
> +		.exit_tfm	= spacc_hash_exit_tfm,
> +
> +		.halg.base = {
> +			.cra_priority	= 300,
> +			.cra_module	= THIS_MODULE,
> +			.cra_ctxsize	= sizeof(struct spacc_crypto_ctx),
> +			.cra_reqsize	= sizeof(struct spacc_crypto_reqctx),
> +			.cra_destroy	= spacc_hash_alg_destroy,
> +			.cra_flags	= CRYPTO_ALG_TYPE_AHASH		|
> +					  CRYPTO_ALG_ASYNC		|
> +					  CRYPTO_AHASH_ALG_BLOCK_ONLY	|
> +					  CRYPTO_ALG_OPTIONAL_KEY
> +		},
> +	},
> +	.op = {
> +		.do_one_request = spacc_hash_do_one_request,
> +	},
> +};

[Severity: High]
spacc_hash_template is shared across all 18 algorithms in possible_hashes[],
unconditionally setting .setkey = spacc_hash_setkey and
CRYPTO_ALG_OPTIONAL_KEY in .cra_flags.

Because CRYPTO_ALG_OPTIONAL_KEY is set, ahash_set_needkey() never sets
CRYPTO_TFM_NEED_KEY on keyed algorithms (cmac(sm4), xcbc(sm4), and hmac(*)).
If a caller invokes crypto_ahash_digest() without calling
crypto_ahash_setkey() (or after a 0-length HMAC setkey()),
spacc_hash_try_hw() sees tctx->keylen == 0, skips spacc_write_context(), and
runs the MAC in hardware using whatever stale key bytes remain in
ctx->hash_key instead of returning -ENOKEY.

Conversely, setting .setkey on the unkeyed hash algorithms (md5, sha1,
sha2*, sha3-*) causes crypto_hash_alg_has_setkey() to report that unkeyed
digests require a key.

Should CRYPTO_ALG_OPTIONAL_KEY be dropped, with .setkey only populated for
the keyed MAC and HMAC algorithms, and should spacc_hash_try_hw() still
program a zero-length key when a valid 0-length HMAC key is set?

> +
> +static int spacc_register_hash(struct spacc_alg *salg)
> +{
> +	int rc = 0;
> +	struct spacc_priv *priv = dev_get_drvdata(salg->dev);
> +
> +	salg->calg = &salg->alg.hash.base.halg.base;
> +	salg->alg.hash = spacc_hash_template;
> +
> +	rc = spacc_init_calg(salg->calg, salg->mode);
> +	if (rc)
> +		return rc;
> +
> +	salg->alg.hash.base.halg.digestsize = salg->mode->hashlen;
> +	salg->alg.hash.base.halg.statesize = HASH_MAX_STATESIZE;
> +
> +	rc = crypto_engine_register_ahash(&salg->alg.hash);

[Severity: High]
In spacc_register_hash(), salg->alg.hash.base.halg.statesize is set to
HASH_MAX_STATESIZE while spacc_hash_template sets
CRYPTO_AHASH_ALG_BLOCK_ONLY.

When crypto_engine_register_ahash() calls ahash_prepare_alg(), the crypto
core adds cra_blocksize + 1 to statesize when CRYPTO_AHASH_ALG_BLOCK_ONLY is
set, making crypto_ahash_statesize(tfm) exceed HASH_MAX_STATESIZE.

As a result, any virtual-address request in ahash_do_req_chain() (such as
crypto/hmac.c wrapping SPAcc sha3 algorithms) hits:

    if (crypto_ahash_statesize(tfm) > HASH_MAX_STATESIZE)
        return -ENOSYS;

and fails, while callers of crypto_ahash_export() with a HASH_MAX_STATESIZE
buffer will have cra_blocksize + 1 bytes written past the end of their
buffer by ahash_save_req().

Since spacc_hash_init(), spacc_hash_update(), spacc_hash_final(), and
spacc_hash_finup() delegate incremental hashing to the software fallback
rather than implementing block-only partial-block return semantics, should
CRYPTO_AHASH_ALG_BLOCK_ONLY be removed from spacc_hash_template?

> +	if (rc < 0)
> +		return rc;
> +
> +	guard(mutex)(&priv->hash_alg_mutex);
> +	list_add(&salg->list, &priv->hash_alg_list);
> +
> +	return 0;
> +}
> +
> +int spacc_probe_hashes(struct platform_device *spacc_pdev)
> +{

[ ... ]

> +			salg->mode = &possible_hashes[index];
> +			salg->dev = &spacc_pdev->dev;
> +
> +			rc = spacc_register_hash(salg);
> +			if (rc < 0) {
> +				kfree(salg);
> +				continue;
> +			}

[Severity: High]
Can this cause a double free of salg when spacc_register_hash() fails?

spacc_register_hash() installs spacc_hash_alg_destroy() (which calls
kfree(salg)) as cra_destroy and then calls
crypto_engine_register_ahash(&salg->alg.hash) -> crypto_register_ahash() ->
crypto_register_alg().

In crypto_register_alg(), once crypto_check_alg() initializes cra_refcnt to
1, any failure in __crypto_register_alg() (such as -EEXIST or -ENOMEM) calls
crypto_alg_put(alg) -> crypto_destroy_alg(alg) -> alg->cra_destroy(alg),
which already frees salg via spacc_hash_alg_destroy() before returning the
error to spacc_probe_hashes(), where kfree(salg) is called a second time.

> +
> +			registered++;
> +		}
> +	}
> +
> +	return registered;
> +
> +err_unregister:
> +	spacc_unregister_hash_algs(priv);
> +	return rc;
> +}
> +
> +int spacc_unregister_hash_algs(struct spacc_priv *priv)
> +{
> +	struct spacc_alg *salg, *tmp;
> +
> +	if (!priv)
> +		return 0;
> +
> +	guard(mutex)(&priv->hash_alg_mutex);
> +
> +	list_for_each_entry_safe(salg, tmp, &priv->hash_alg_list, list) {
> +		list_del(&salg->list);
> +		crypto_engine_unregister_ahash(&salg->alg.hash);
> +	}
> +
> +	return 0;

[Severity: Low]
spacc_unregister_hash_algs() is missing its closing brace at the end of
drivers/crypto/dwc-spacc/spacc_ahash.c, which causes a syntax error when
building the driver.

> diff --git a/drivers/crypto/dwc-spacc/spacc_core.c b/drivers/crypto/dwc-spacc/spacc_core.c
> new file mode 100644
> index 0000000000000..aa5fbe9a3e074
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/spacc_core.c

[ ... ]

> +int spacc_sg_to_ddt(struct device *dev, struct scatterlist *sg,
> +		    int nbytes, struct pdu_ddt *ddt, int dma_direction)
> +{
> +	int i;
> +	int nents;
> +	int rc = 0;
> +	int orig_nents;
> +	struct scatterlist *sgl;
> +	struct scatterlist *sg_entry;
> +
> +	/* Count the SG entries needed to cover the requested length. */
> +	orig_nents = sg_nents_for_len(sg, nbytes);
> +	if (orig_nents <= 0)
> +		return orig_nents;
> +
> +	/*
> +	 * Ignore a trailing zero-length SG entry if present.
> +	 * Some callers use it as a terminator.
> +	 */
> +	if (orig_nents > 1) {
> +		sgl = sg_last(sg, orig_nents);
> +		if (sgl->length == 0)
> +			orig_nents--;
> +	}

[Severity: High]
Won't calling sg_last(sg, orig_nents) here trigger BUG_ON(!sg_is_last(ret))
in sg_last() whenever the scatterlist contains more entries than the prefix
needed to cover nbytes?

sg_nents_for_len(sg, nbytes) stops as soon as the accumulated length reaches
nbytes and returns the number of entries in that prefix. If orig_nents > 1
and the caller's scatterlist has any entries after the orig_nents-th entry
(including a trailing zero-length entry after entries that already sum to
nbytes, or a buffer larger than req->nbytes), the orig_nents-th entry does
not have the SG_END marker set, causing sg_last() to hit BUG_ON().

[ ... ]

> +int spacc_init(void __iomem *baseaddr, struct spacc_device *spacc,
> +	       struct pdu_info *info, struct device *dev)
> +{
> +	int ret;
> +#ifdef CONFIG_CRYPTO_DEV_SPACC_CONFIG_DEBUG
> +	unsigned long id;
> +	char version_string[3][16]  = { "SPACC", "SPACC-PDU" };
> +	char idx_string[2][16]      = { "(Normal Port)", "(Secure Port)" };
> +	char dma_type_string[4][16] = { "Unknown", "Scattergather", "Linear",
> +					"Unknown" };
> +#endif

[ ... ]

> +	if (spacc->config.version >= 0x48)
> +		dev_dbg(spacc->dptr,
> +			"   TYPE       : %lx (%s)\n", SPACC_ID_TYPE(id),
> +			version_string[SPACC_ID_TYPE(id) & 3]);

[Severity: Medium]
Does indexing version_string[] with (SPACC_ID_TYPE(id) & 3) read out of
bounds when SPACC_ID_TYPE(id) is 3?

version_string is declared as char version_string[3][16] with only 3
elements (and only 2 initialized), whereas SPACC_ID_TYPE(id) & 3 can
evaluate to 3.

[ ... ]

> +int spacc_compute_xcbc_key(struct spacc_device *spacc, int mode_id,
> +			   int job_idx, const unsigned char *key,
> +			   int keylen, unsigned char *xcbc_out)
> +{

[ ... ]

> +			if (wait_event_interruptible(job->waitq,
> +						     job->job_done))
> +				wait_event(job->waitq,
> +					/*
> +					 * Pairs with smp_store_release() of
> +					 * job_done in completion path.
> +					 */
> +					 smp_load_acquire(&job->job_done));
> +
> +			job->job_done = 0;
> +			err = job->job_err;
> +			if (err != 0)
> +				goto xcbc_err;
> +		}
> +	} else {
> +		/*
> +		 * Do the 48 bytes as a single SPAcc job this is the ideal case
> +		 * but only possible if ECB was enabled in the core
> +		 */
> +		spacc_write_context(spacc, handle, SPACC_CRYPTO_OPERATION,
> +				    key, keylen, iv, 16);
> +		err = spacc_packet_enqueue_ddt(spacc, handle, &ddt, &ddt, 48,
> +					       0, 0, 0, 0, 0, false);
> +		if (err != 0)
> +			goto xcbc_err;
> +
> +		if (wait_event_interruptible(job->waitq, job->job_done))
> +
> +			wait_event(job->waitq,
> +				  /*
> +				   * Pairs with smp_store_release() of
> +				   * job_done in completion path.
> +				   */
> +				   smp_load_acquire(&job->job_done));
> +
> +		job->job_done = 0;
> +		err = job->job_err;
> +		if (err != 0)
> +			goto xcbc_err;
> +	}

[Severity: Medium]
In both branches of spacc_compute_xcbc_key(), wait_event_interruptible()
checks plain job->job_done and only falls back to wait_event(...,
smp_load_acquire(&job->job_done)) when interrupted by a signal.

When wait_event_interruptible() succeeds without a signal (returning 0), the
wait_event() call is skipped. If job->job_done is already set before
wait_event_interruptible() is entered, its fast path reads job->job_done
without taking waitq.lock and without an acquire barrier to pair with the
smp_store_release(&job->job_done, 1) in spacc_pop_one_job() and
spacc_process_jb().

Should wait_event_interruptible() also use smp_load_acquire(&job->job_done)
(or simply use wait_event(job->waitq, smp_load_acquire(&job->job_done))
directly)?

> diff --git a/drivers/crypto/dwc-spacc/spacc_device.c b/drivers/crypto/dwc-spacc/spacc_device.c
> new file mode 100644
> index 0000000000000..af95be0a8ebc1
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/spacc_device.c

[ ... ]

> +static void spacc_crypto_remove(struct platform_device *pdev)
> +{
> +	struct spacc_priv *priv = platform_get_drvdata(pdev);
> +	unsigned long flags;
> +
> +	/*
> +	 * Set before anything else: sysfs unbind (unlike rmmod) has no
> +	 * built-in wait for existing tfms to be freed, so a request can
> +	 * reach spacc_hash_digest() at any point during this function.
> +	 * This flag, checked under the same lock there, blocks any new
> +	 * submission attempt from this point on.
> +	 */
> +	spin_lock_irqsave(&priv->spacc.lock, flags);
> +	priv->removing = true;
> +	spin_unlock_irqrestore(&priv->spacc.lock, flags);
> +
> +	/* Stop the crypto API from handing out any new tfms. */
> +	spacc_unregister_algs(priv);
> +
> +	if (priv->engine)
> +		crypto_engine_exit(priv->engine);
> +
> +	wait_event(priv->drain_wq, !atomic_read(&priv->dma_ref_cnt));
> +
> +	/* Nothing can dispatch new work past this point. */
> +	spin_lock_irqsave(&priv->spacc.lock, flags);
> +	spacc_irq_glbl_disable(&priv->spacc);
> +	spin_unlock_irqrestore(&priv->spacc.lock, flags);
> +
> +	devm_free_irq(&pdev->dev, priv->irq, &pdev->dev);
> +
> +	if (priv->spacc_wq)
> +		destroy_workqueue(priv->spacc_wq);
> +
> +	/* No job can still reference this pool past the drain above. */
> +	dma_pool_destroy(priv->hash_pool);
> +	priv->hash_pool = NULL;
> +
> +	spacc_remove(pdev);
> +	platform_set_drvdata(pdev, NULL);
> +}

[Severity: High]
Can unbinding the device in spacc_crypto_remove() race with active requests
and surviving tfms, leading to a use-after-free or a leaked crypto_engine
kthread?

First, in spacc_hash_digest():

    spin_lock_irqsave(&priv->spacc.lock, flags);
    removing = priv->removing;
    spin_unlock_irqrestore(&priv->spacc.lock, flags);

    if (removing)
        return -ENODEV;

    return crypto_transfer_hash_request_to_engine(priv->engine, req);

priv->spacc.lock is dropped before crypto_transfer_hash_request_to_engine()
is called, and priv->dma_ref_cnt is not incremented until
spacc_hash_do_one_request() after spacc_hash_try_hw() completes. If
spacc_crypto_remove() runs in that window, wait_event(priv->drain_wq, ...)
can see dma_ref_cnt == 0 and proceed to tear down priv->engine,
priv->hash_pool, and spacc while the request is still in flight.

Second, crypto_engine_exit(priv->engine) is called before
wait_event(priv->drain_wq, ...). Because retry_support is false,
engine->busy remains true until spacc_digest_cb() calls
crypto_finalize_hash_request(); if an in-flight job takes longer than
crypto_engine_stop()'s 200ms timeout, crypto_engine_exit() returns early
without calling kthread_destroy_worker(), leaking the worker kthread.

Third, when unbound via sysfs, existing crypto_ahash tfms can outlive
spacc_crypto_remove(). Because priv is allocated with devm_kzalloc(), devres
frees priv once spacc_crypto_remove() returns (get_device() in
spacc_hash_init_tfm() pins struct device, not devres resources), and
spacc_remove() frees spacc->ctx and spacc->job before
platform_set_drvdata(pdev, NULL). A concurrent or later call to
spacc_hash_exit_tfm(), spacc_hash_setkey(), or spacc_hash_digest() can read
a non-NULL drvdata pointer and dereference freed spacc->job, spacc->ctx, or
priv.

> diff --git a/drivers/crypto/dwc-spacc/spacc_interrupt.c b/drivers/crypto/dwc-spacc/spacc_interrupt.c
> new file mode 100644
> index 0000000000000..68dd049a5f65a
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/spacc_interrupt.c

[ ... ]

> +u32 spacc_process_irq(struct spacc_device *spacc)
> +{
> +	u32 irq_status;
> +	int x, cmd_max;
> +	unsigned long lock_flag;
> +
> +	spin_lock_irqsave(&spacc->lock, lock_flag);
> +
> +	irq_status = readl(spacc->regmap + SPACC_REG_IRQ_STAT);
> +
> +	/* clear interrupt pin and run registered callback */
> +	if (irq_status & SPACC_IRQ_STAT_STAT) {
> +		SPACC_IRQ_STAT_CLEAR_STAT(spacc);
> +		if (spacc->op_mode == SPACC_OP_MODE_IRQ) {
> +			spacc->config.fifo_cnt <<= 2;
> +			spacc->config.fifo_cnt = min(spacc->config.fifo_cnt,
> +					spacc->config.stat_fifo_depth);
> +
> +			/* update fifo count to allow more stats to pile up */
> +			spacc_irq_stat_enable(spacc, spacc->config.fifo_cnt);
> +
> +			/* re-enable CMD0 empty interrupt */
> +			spacc_irq_cmdx_enable(spacc, 0, 0);
> +		}
> +
> +		/* Re-enable the watchdog interrupt */
> +		if (spacc->op_mode == SPACC_OP_MODE_IRQ &&
> +				spacc->wd_cnt_limit) {
> +			spacc_irq_stat_wd_enable(spacc);
> +			spacc->wdcnt = 0;
> +			spacc->op_mode = SPACC_OP_MODE_WD;
> +			spacc->wd_cnt_limit = false;
> +		}
> +
> +		if (spacc->irq_cb_stat)
> +			spacc->irq_cb_stat(spacc);
> +	}

[Severity: High]
Can this sequence in spacc_process_irq() trigger an unhandled CMD0 interrupt
storm when recovering from watchdog limit fallback?

When spacc->wdcnt reaches SPACC_WD_LIMIT in SPACC_OP_MODE_WD,
spacc_process_irq() switches spacc->op_mode to SPACC_OP_MODE_IRQ and sets
spacc->wd_cnt_limit = true.

On the next SPACC_IRQ_STAT_STAT interrupt, the first block sees
spacc->op_mode == SPACC_OP_MODE_IRQ and calls:

    spacc_irq_cmdx_enable(spacc, 0, 0);

Immediately after, the second block sees spacc->op_mode == SPACC_OP_MODE_IRQ
&& spacc->wd_cnt_limit and switches spacc->op_mode back to SPACC_OP_MODE_WD
without disabling CMD0 interrupts.

Because the CMDX servicing loop below only runs when spacc->op_mode ==
SPACC_OP_MODE_IRQ:

    if (spacc->op_mode == SPACC_OP_MODE_IRQ) {
        cmd_max = (spacc->config.is_qos ? SPACC_CMDX_MAX_QOS :
                SPACC_CMDX_MAX);
        for (x = 0; x < cmd_max; x++) {
            if (irq_status & SPACC_IRQ_STAT_CMDX(x)) {
                ...

once SPACC_IRQ_STAT_CMDX(0) asserts in SPACC_REG_IRQ_STAT,
spacc_process_irq() will never clear or disable it while spacc_irq_handler()
keeps returning IRQ_HANDLED. Should the two if-blocks in the
SPACC_IRQ_STAT_STAT handler be an if / else if (checking wd_cnt_limit
first)?

[ ... ]

> +/* Function to run callbacks in the IRQ handler */
> +irqreturn_t spacc_irq_handler(int irq, void *dev)
> +{
> +	struct spacc_priv *priv = platform_get_drvdata(to_platform_device(dev));
> +	struct spacc_device *spacc = &priv->spacc;
> +
> +	if (spacc->config.oldtimer != spacc->config.timer) {
> +		spacc->config.wd_timer = spacc->config.timer;
> +		spacc_set_wd_count(spacc, spacc->config.wd_timer);
> +		spacc->config.oldtimer = spacc->config.timer;
> +	}
> +
> +	/* check irq flags and process as required */
> +	if (!spacc_process_irq(spacc))
> +		return IRQ_NONE;
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
There appear to be a few concurrency and shared-IRQ handling issues between
spacc_irq_handler(), spacc_process_irq(), and spacc_init_device():

1. In spacc_irq_handler(), spacc->config.oldtimer, spacc->config.wd_timer,
and SPACC_REG_STAT_WD_CTRL are updated without holding spacc->lock and
before checking whether the interrupt came from this device on an
IRQF_SHARED line, racing with spacc_process_irq() and
spacc_pop_packets_ex().

2. In spacc_init_device(), after devm_request_irq(..., IRQF_SHARED, ...) and
spacc_irq_glbl_enable() are called, the driver modifies spacc.op_mode,
callback pointers, wd_timer, SPACC_REG_IRQ_CTRL, and SPACC_REG_IRQ_EN
without holding spacc.lock.

3. In spacc_process_irq(), irq_status is read from SPACC_REG_IRQ_STAT
without masking against SPACC_REG_IRQ_EN, and the raw irq_status is returned
to spacc_irq_handler(). If any status bits are set for a mode that is not
currently active in spacc->op_mode, spacc_irq_handler() returns IRQ_HANDLED
without clearing those bits.

Could the timer update and IRQ configuration changes be serialized under
spacc->lock, and should spacc_process_irq() only return IRQ_HANDLED for
enabled interrupt bits that it actually serviced?

> diff --git a/drivers/crypto/dwc-spacc/spacc_manager.c b/drivers/crypto/dwc-spacc/spacc_manager.c
> new file mode 100644
> index 0000000000000..0ee40fa7678cf
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/spacc_manager.c

[ ... ]

> +static int spacc_ctx_request(struct spacc_device *spacc,
> +			     int ctx_id, int ncontig)
> +{
> +	int ret = 0;
> +	int x, y, count;
> +
> +	if (!spacc)
> +		return -EINVAL;
> +
> +	if (ctx_id < -1 || ctx_id >= spacc->config.num_ctx)
> +		return -EINVAL;
> +
> +	if (ncontig < 1 || ncontig > spacc->config.num_ctx)
> +		return -EINVAL;

[ ... ]

> +/* Get a new job id and use a specific ctx_idx or -1 for a new one */
> +int spacc_job_request(struct spacc_device *spacc, int ctx_idx)
> +{
> +	int x, ret = 0;
> +	struct spacc_job *job;
> +	unsigned long lock_flag;
> +
> +	if (!spacc)
> +		return -EINVAL;
> +
> +	spin_lock_irqsave(&spacc->lock, lock_flag);
> +
> +	/* find the first available job id */
> +	for (x = 0; x < SPACC_MAX_JOBS; x++) {
> +		job = &spacc->job[x];
> +		if (job->job_used == SPACC_JOB_IDX_UNUSED) {
> +			job->job_used = x;
> +			break;
> +		}
> +	}
> +
> +	if (x == SPACC_MAX_JOBS)
> +		ret = -1;
> +	else {
> +		/* associate a single context to go with job */
> +		ret = spacc_ctx_request(spacc, ctx_idx, 1);
> +		if (ret != -1) {
> +			job->ctx_idx = ret;
> +			ret = x;
> +		} else
> +			job->job_used = SPACC_JOB_IDX_UNUSED;
> +	}

[Severity: Medium]
In spacc_job_request(), if spacc_ctx_request() fails validation and returns
-EINVAL (for instance, if num_ctx is 0 or ctx_idx is out of range), checking
ret != -1 treats -EINVAL as a valid context index and assigns it to
job->ctx_idx.

Should spacc_job_request() check ret >= 0 instead of ret != -1?

[ ... ]

> +}

—
AI reviewed your patch. Please fix the bug or emailuar@kernel.org if the issue is a false positive.
ോട്

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019162457.1000115-1-pavitrakumarm@vayavyalabs.com?part=2

  reply	other threads:[~2026-10-19 17:03 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 16:24 [PATCH v26 0/4] crypto: spacc - Add SPAcc Crypto Driver Pavitrakumar Managutte
2026-10-19 16:24 ` [PATCH v26 1/4] dt-bindings: crypto: Document support for SPAcc Pavitrakumar Managutte
2026-10-19 16:24 ` [PATCH v26 2/4] crypto: spacc - Add SPAcc ahash support Pavitrakumar Managutte
2026-10-19 17:03   ` sashiko-bot [this message]
2026-10-19 16:24 ` [PATCH v26 3/4] crypto: spacc - add spacc autodetect support Pavitrakumar Managutte
2026-10-19 16:24 ` [PATCH v26 4/4] crypto: spacc - Add SPAcc Kconfig and Makefile Pavitrakumar Managutte
2026-10-19 16:59   ` sashiko-bot
2026-10-20  6:17   ` kernel test robot

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=sashiko-outbox-177985@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=pavitrakumarm@vayavyalabs.com \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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