Skip to content

Commit a8b074a

Browse files
committed
implement streaming HMAC.update() interface
Depending on whether Python is compiled in free-threaded mode or not, and depending on the length of the message fed to HMAC, a lock is temporarily acquired on the object to avoid concurrent state modifications.
1 parent db8e2fd commit a8b074a

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

Modules/clinic/hmacmodule.c.h

Lines changed: 57 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/hmacmodule.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ class _hmac.HMAC "HMACObject *" "clinic_state()->hmac_type"
384384
//
385385
// - Helpers with the "_hacl" prefix are thin wrappers around HACL* functions.
386386
// Buffer lengths given as inputs should fit on 32-bit integers.
387+
//
388+
// - Helpers with the "hmac_" prefix act on HMAC objects and accept buffers
389+
// whose length fits on 32-bit or 64-bit integers (depending on the host
390+
// machine).
387391

388392
/*
389393
* Handle the HACL* exit code.
@@ -588,6 +592,99 @@ has_uint32_t_buffer_length(const Py_buffer *buffer)
588592

589593
// --- HMAC object ------------------------------------------------------------
590594

595+
/*
596+
* Update the HMAC object with the given buffer.
597+
*
598+
* This unconditionally acquires the lock on the HMAC object.
599+
*
600+
* On DEBUG builds, each update() call is verified.
601+
* On other builds, only the last update() call is verified.
602+
*
603+
* Return 0 on success and -1 on failure.
604+
*/
605+
static int
606+
hmac_update_state_with_lock(HMACObject *self, uint8_t *buf, Py_ssize_t len)
607+
{
608+
int res = 0;
609+
Py_BEGIN_ALLOW_THREADS
610+
PyMutex_Lock(&self->mutex); // unconditionally acquire a lock
611+
Py_HMAC_HACL_UPDATE(self->state, buf, len, self->name, goto error);
612+
goto done;
613+
#ifndef NDEBUG
614+
error:
615+
res = -1;
616+
#else
617+
Py_UNREACHABLE();
618+
#endif
619+
done:
620+
PyMutex_Unlock(&self->mutex);
621+
Py_END_ALLOW_THREADS
622+
return res;
623+
}
624+
625+
/*
626+
* Update the HMAC object with the given buffer.
627+
*
628+
* This conditionally acquires the lock on the HMAC object.
629+
*
630+
* On DEBUG builds, each update() call is verified.
631+
* On other builds, only the last update() call is verified.
632+
*
633+
* Return 0 on success and -1 on failure.
634+
*/
635+
static int
636+
hmac_update_state_cond_lock(HMACObject *self, uint8_t *buf, Py_ssize_t len)
637+
{
638+
ENTER_HASHLIB(self); // conditionally acquire a lock
639+
Py_HMAC_HACL_UPDATE(self->state, buf, len, self->name, goto error);
640+
LEAVE_HASHLIB(self);
641+
return 0;
642+
643+
#ifndef NDEBUG
644+
error:
645+
LEAVE_HASHLIB(self);
646+
return -1;
647+
#else
648+
Py_UNREACHABLE();
649+
#endif
650+
}
651+
652+
/*
653+
* Update the internal HMAC state with the given buffer.
654+
*
655+
* Return 0 on success and -1 on failure.
656+
*/
657+
static inline int
658+
hmac_update_state(HMACObject *self, uint8_t *buf, Py_ssize_t len)
659+
{
660+
assert(buf != 0);
661+
assert(len >= 0);
662+
return len == 0
663+
? 0 /* nothing to do */
664+
: len < HASHLIB_GIL_MINSIZE
665+
? hmac_update_state_cond_lock(self, buf, len)
666+
: hmac_update_state_with_lock(self, buf, len);
667+
}
668+
669+
/*[clinic input]
670+
_hmac.HMAC.update
671+
672+
msg as msgobj: object
673+
674+
Update the HMAC object with the given message.
675+
[clinic start generated code]*/
676+
677+
static PyObject *
678+
_hmac_HMAC_update_impl(HMACObject *self, PyObject *msgobj)
679+
/*[clinic end generated code: output=962134ada5e55985 input=7c0ea830efb03367]*/
680+
{
681+
Py_buffer msg;
682+
GET_BUFFER_VIEW_OR_ERROUT(msgobj, &msg);
683+
int rc = hmac_update_state(self, msg.buf, msg.len);
684+
PyBuffer_Release(&msg);
685+
return rc < 0 ? NULL : Py_None;
686+
}
687+
591688
/*[clinic input]
592689
@getter
593690
_hmac.HMAC.name
@@ -661,6 +758,7 @@ HMACObject_traverse(PyObject *self, visitproc visit, void *arg)
661758
}
662759

663760
static PyMethodDef HMACObject_methods[] = {
761+
_HMAC_HMAC_UPDATE_METHODDEF
664762
{NULL, NULL, 0, NULL} /* sentinel */
665763
};
666764

0 commit comments

Comments
 (0)