Skip to content

Reject invalid API inputs before unsafe work#1231

Open
fegge wants to merge 1 commit into
mainfrom
ptp/api-input-validation-and-memory-safety
Open

Reject invalid API inputs before unsafe work#1231
fegge wants to merge 1 commit into
mainfrom
ptp/api-input-validation-and-memory-safety

Conversation

@fegge

@fegge fegge commented Jun 26, 2026

Copy link
Copy Markdown

Summary

  • Reject malformed public API inputs before allocation, copying, or length arithmetic that depends on those inputs.
  • Tighten external-mu, context, signature-length, and signed-message overflow checks.
  • Add allocator and functional regression coverage for the rejected inputs.

Validation

  • clang-format -i mldsa/src/sign.c mldsa/src/sign.h mldsa/mldsa_native.h test/src/test_alloc.c test/src/test_mldsa.c: passed
  • git diff --check -- mldsa/src/sign.c mldsa/src/sign.h mldsa/mldsa_native.h test/src/test_alloc.c test/src/test_mldsa.c: passed
  • make run_alloc run_func -j4: passed
  • Not run: ./scripts/format because nixpkgs-fmt is not installed in this local environment
  • Not run: ./scripts/lint because shfmt is not installed in this local environment

Fixes #1212


This work was completed by Trail of Bits as part of the Patch The Planet project in collaboration with OpenAI. The issue was identified primarily by the Codex coding agent, and manually reviewed before submission.

Signed-off-by: Fredrik Dahlgren <fredrik.dahlgren@trailofbits.com>
Comment thread mldsa/src/sign.c
Comment on lines +1063 to 1072

if (ctxlen > 255 || (ctx == NULL && ctxlen != 0))
{
*siglen = 0;
mld_memset(sig, 0, MLDSA_CRYPTO_BYTES);
return MLD_ERR_FAIL;
}

MLD_ALLOC(pre, uint8_t, MLD_DOMAIN_SEPARATION_MAX_BYTES, context);
MLD_ALLOC(rnd, uint8_t, MLDSA_RNDBYTES, context);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check must happen after the allocations for strict C90 compliance. Which has the benefit that we can reuse the cleanup section.

Comment thread mldsa/src/sign.c
Comment on lines +1172 to +1177
if (mlen > SIZE_MAX - MLDSA_CRYPTO_BYTES)
{
*smlen = 0;
return MLD_ERR_FAIL;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this change.

If a user wants to call us with bad parameters, they can. They can just pass a nonsense (non-NULL) address, and we'll crash.

So, if we do want to conduct input validation, I think it should be input validation for the sake of preventing accidental misuse, not deliberate/adversarial one. The above check doesn't fall under that category, IMO, so I don't think we should add it.

Comment thread mldsa/src/sign.c
Comment on lines +1212 to 1225
if (siglen != MLDSA_CRYPTO_BYTES || (externalmu && mlen != MLDSA_CRHBYTES))
{
return MLD_ERR_FAIL;
}

MLD_ALLOC(buf, uint8_t, (MLDSA_K * MLDSA_POLYW1_PACKEDBYTES), context);
MLD_ALLOC(mu, uint8_t, MLDSA_CRHBYTES, context);
MLD_ALLOC(c, uint8_t, MLDSA_CTILDEBYTES, context);
MLD_ALLOC(c2, uint8_t, MLDSA_CTILDEBYTES, context);
MLD_ALLOC(z, mld_polyvecl, 1, context);
MLD_ALLOC(cp, mld_poly, 1, context);
MLD_ALLOC(mat, mld_polymat, 1, context);
MLD_ALLOC(w1, mld_poly, 1, context);
MLD_ALLOC(tmp, mld_poly, 1, context);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: It's for C90 compliance that we need to conduct the declarations/allocations upfront.

Comment thread mldsa/src/sign.c
size_t phlen, const uint8_t *ctx, size_t ctxlen, int hashalg)
{
if (ctxlen > 255)
if (ctxlen > 255 || (ctx == NULL && ctxlen != 0))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer input validation to happen at the top-level API, even if it means a bit of code-duplication.

Comment thread mldsa/src/sign.h
requires(memory_no_alias(siglen, sizeof(size_t)))
requires(memory_no_alias(m, mlen))
requires(ctxlen <= MLD_MAX_BUFFER_SIZE)
requires(ctxlen <= 255)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear if this should be changed. There are two valid purposes for the CBMC specs:

  1. MUST: Document the conditions under which the function is safe to be called and has well-defined behavior. ctxlen < = 255 is not part of that.
  2. MAY: Document the success conditions for the function. ctx <= 255 is (now) part of that.

The CBMC spec differs in this regard from the natural language documentation, which should be focused on 2. -- the success case.

I'm thus leaning towards changing Should be <= 255 to Must be <= 255 in the dcoumentation, but keep the CBMC contract as-is.

Comment thread mldsa/src/sign.h
Comment on lines +345 to +346
* @param mlen Length of message. Must be <= SIZE_MAX -
* MLDSA_CRYPTO_BYTES.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to drop Must be <= SIZE_MAX - MLDSA_CRYPTO_BYTES -- this is already implied by the validity assumption and the fact that sm has size MLDSA_CRYPTO_BYTES + mlen.

Comment thread test/src/test_alloc.c
} \
} while (0)

#define TEST_NO_ALLOC_FAILURE(test_name, call, expected_rc) \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed once we reorder the checks past allocation.

@hanno-becker hanno-becker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for initiating a discussion on input validation, @fegge.

So far, we have deliberately not conducted input validation, but instead simply assumed that the user passes correct arguments. As mentioned in the comments, we ultimately have to do that, because true pre-conditions such as pointer-validity cannot be checked. For that reason, I think the only input validation one should consider is that aiming to catch accidental misuse.

I'd be OK with consistently adding len == 0 || ptr != NULL checks as an safe-guard against accidental passage of bad length or NULL pointers. If we decide to do so, this should be applied to all top-level APIs and all pointers consistently, perhaps even through some shared macro.

@mkannwischer wdyt

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.

Public APIs perform unsafe work before validating available input lengths

2 participants