Skip to content

Commit 2d02577

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 4a0e9cd commit 2d02577

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
@@ -383,6 +383,10 @@ class _hmac.HMAC "HMACObject *" "clinic_state()->hmac_type"
383383
//
384384
// - Helpers with the "_hacl" prefix are thin wrappers around HACL* functions.
385385
// Buffer lengths given as inputs should fit on 32-bit integers.
386+
//
387+
// - Helpers with the "hmac_" prefix act on HMAC objects and accept buffers
388+
// whose length fits on 32-bit or 64-bit integers (depending on the host
389+
// machine).
386390

387391
/*
388392
* Handle the HACL* exit code.
@@ -585,6 +589,99 @@ has_uint32_t_buffer_length(const Py_buffer *buffer)
585589

586590
// --- HMAC object ------------------------------------------------------------
587591

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

660757
static PyMethodDef HMACObject_methods[] = {
758+
_HMAC_HMAC_UPDATE_METHODDEF
661759
{NULL, NULL, 0, NULL} /* sentinel */
662760
};
663761

0 commit comments

Comments
 (0)