]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix(c): solve undefined behaviour caused by unaligned access 735/head
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 11 Feb 2024 01:34:20 +0000 (02:34 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 20 Apr 2024 21:46:02 +0000 (23:46 +0200)
Close #734

docs/news.rst
psycopg_c/psycopg_c/_psycopg/copy.pyx
psycopg_c/psycopg_c/types/array.pyx
psycopg_c/psycopg_c/types/datetime.pyx
psycopg_c/psycopg_c/types/numeric.pyx

index 494ebaa19b77dd00f4b9bf00a15619ff31b649d0..9a62899bb438a71382fe1ba0b725b28c45cca3ad 100644 (file)
@@ -46,9 +46,10 @@ Psycopg 3.2 (unreleased)
 .. __: https://numpy.org/doc/stable/reference/arrays.scalars.html#built-in-scalar-types
 
 
-Psycopg 3.1.19
-^^^^^^^^^^^^^^
+Psycopg 3.1.19 (unreleased)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+- Fix unaligned access undefined behaviour in C extension (:ticket:`#734`).
 - Fix excessive stripping of error message prefixes (:ticket:`#752`).
 - Allow to specify the ``connect_timeout`` connection parameter as float
   (:ticket:`#796`).
index b943095d11cdea669ab8c7df903126150c4befae..e1ca8191f91bc00fcced5cef8d508ea52a1936fb 100644 (file)
@@ -191,7 +191,8 @@ def parse_row_binary(data, tx: Transformer) -> Tuple[Any, ...]:
     _buffer_as_string_and_size(data, <char **>&ptr, &bufsize)
     cdef unsigned char *bufend = ptr + bufsize
 
-    cdef uint16_t benfields = (<uint16_t *>ptr)[0]
+    cdef uint16_t benfields
+    memcpy(&benfields, ptr, sizeof(benfields))
     cdef int nfields = endian.be16toh(benfields)
     ptr += sizeof(benfields)
     cdef list row = PyList_New(nfields)
index 280d33c28170b0ee50ec3c8def3de68cca99952c..91da57702407438ad816a4d90d812162cfa35a45 100644 (file)
@@ -210,8 +210,9 @@ cdef object _array_load_binary(
     const char *buf, size_t length, Transformer tx, PyObject **row_loader_ptr
 ):
     # head is ndims, hasnull, elem oid
-    cdef uint32_t *buf32 = <uint32_t *>buf
-    cdef int ndims = endian.be32toh(buf32[0])
+    cdef uint32_t buf32
+    memcpy(&buf32, buf, sizeof(buf32))
+    cdef int ndims = endian.be32toh(buf32)
 
     if ndims <= 0:
         return []
@@ -222,17 +223,23 @@ cdef object _array_load_binary(
         )
 
     cdef object oid
+    cdef uint32_t beoid
     if row_loader_ptr[0] == NULL:
-        oid = <Oid>endian.be32toh(buf32[2])
+        memcpy(&beoid, buf + 2 * sizeof(uint32_t), sizeof(beoid))
+        oid = <Oid>endian.be32toh(beoid)
         row_loader_ptr[0] = tx._c_get_loader(<PyObject *>oid, <PyObject *>PQ_BINARY)
 
     cdef Py_ssize_t[MAXDIM] dims
     cdef int i
+    cdef uint32_t bedata
+    cdef char *dimptr = buf + 3 * sizeof(uint32_t)
     for i in range(ndims):
         # Every dimension is dim, lower bound
-        dims[i] = endian.be32toh(buf32[3 + 2 * i])
+        memcpy(&bedata, dimptr, sizeof(bedata))
+        dimptr += 2 * sizeof(uint32_t)
+        dims[i] = endian.be32toh(bedata)
 
-    buf += (3 + 2 * ndims) * sizeof(uint32_t)
+    buf += (3 + 2 * ndims) * sizeof(bedata)
     out = _array_load_binary_rec(ndims, dims, &buf, row_loader_ptr[0])
     return out
 
@@ -242,6 +249,7 @@ cdef object _array_load_binary_rec(
 ):
     cdef const char *buf
     cdef int i
+    cdef uint32_t besize
     cdef int32_t size
     cdef object val
 
@@ -251,8 +259,9 @@ cdef object _array_load_binary_rec(
     if ndims == 1:
         buf = bufptr[0]
         for i in range(nelems):
-            size = <int32_t>endian.be32toh((<uint32_t *>buf)[0])
-            buf += sizeof(uint32_t)
+            memcpy(&besize, buf, sizeof(besize))
+            size = <int32_t>endian.be32toh(besize)
+            buf += sizeof(besize)
             if size == -1:
                 val = None
             else:
index 4b0784bded5cce4661d1a40038d9a614ecde7b21..c11e7b11c26e23afc858a8727c390c3d230c8ce5 100644 (file)
@@ -95,10 +95,10 @@ cdef class DateBinaryDumper(CDumper):
         cdef int32_t days = PyObject_CallFunctionObjArgs(
             date_toordinal, <PyObject *>obj, NULL)
         days -= PG_DATE_EPOCH_DAYS
-        cdef int32_t *buf = <int32_t *>CDumper.ensure_size(
-            rv, offset, sizeof(int32_t))
-        buf[0] = endian.htobe32(days)
-        return sizeof(int32_t)
+        cdef uint32_t bedays = endian.htobe32(days)
+        cdef uint32_t *buf = <uint32_t *>CDumper.ensure_size(rv, offset, sizeof(bedays))
+        memcpy(buf, &bedays, sizeof(bedays))
+        return sizeof(bedays)
 
 
 cdef class _BaseTimeDumper(CDumper):
@@ -168,15 +168,15 @@ cdef class TimeBinaryDumper(_BaseTimeDumper):
     oid = oids.TIME_OID
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
-        cdef int64_t micros = cdt.time_microsecond(obj) + 1000000 * (
+        cdef int64_t us = cdt.time_microsecond(obj) + 1000000 * (
             cdt.time_second(obj)
             + 60 * (cdt.time_minute(obj) + 60 * <int64_t>cdt.time_hour(obj))
         )
+        cdef uint64_t beus = endian.htobe64(us)
 
-        cdef int64_t *buf = <int64_t *>CDumper.ensure_size(
-            rv, offset, sizeof(int64_t))
-        buf[0] = endian.htobe64(micros)
-        return sizeof(int64_t)
+        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus))
+        memcpy(buf, &beus, sizeof(beus))
+        return sizeof(beus)
 
     cpdef upgrade(self, obj, format):
         if not obj.tzinfo:
@@ -192,21 +192,21 @@ cdef class TimeTzBinaryDumper(_BaseTimeDumper):
     oid = oids.TIMETZ_OID
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
-        cdef int64_t micros = cdt.time_microsecond(obj) + 1_000_000 * (
+        cdef int64_t us = cdt.time_microsecond(obj) + 1_000_000 * (
             cdt.time_second(obj)
             + 60 * (cdt.time_minute(obj) + 60 * <int64_t>cdt.time_hour(obj))
         )
+        cdef uint64_t beus = endian.htobe64(us)
 
         off = self._get_offset(obj)
         cdef int32_t offsec = int(PyObject_CallFunctionObjArgs(
             timedelta_total_seconds, <PyObject *>off, NULL))
+        cdef uint32_t beoff = endian.htobe32(-offsec)
 
-        cdef char *buf = CDumper.ensure_size(
-            rv, offset, sizeof(int64_t) + sizeof(int32_t))
-        (<int64_t *>buf)[0] = endian.htobe64(micros)
-        (<int32_t *>(buf + sizeof(int64_t)))[0] = endian.htobe32(-offsec)
-
-        return sizeof(int64_t) + sizeof(int32_t)
+        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus) + sizeof(beoff))
+        memcpy(buf, &beus, sizeof(beus))
+        memcpy(buf + sizeof(beus), &beoff, sizeof(beoff))
+        return sizeof(beus) + sizeof(beoff)
 
 
 cdef class _BaseDatetimeDumper(CDumper):
@@ -268,13 +268,14 @@ cdef class DatetimeBinaryDumper(_BaseDatetimeDumper):
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
         delta = obj - pg_datetimetz_epoch
 
-        cdef int64_t micros = cdt.timedelta_microseconds(delta) + 1_000_000 * (
+        cdef int64_t us = cdt.timedelta_microseconds(delta) + 1_000_000 * (
             86_400 * <int64_t>cdt.timedelta_days(delta)
                 + <int64_t>cdt.timedelta_seconds(delta))
+        cdef uint64_t beus = endian.htobe64(us)
 
-        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(int64_t))
-        (<int64_t *>buf)[0] = endian.htobe64(micros)
-        return sizeof(int64_t)
+        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus))
+        memcpy(buf, &beus, sizeof(beus))
+        return sizeof(beus)
 
     cpdef upgrade(self, obj, format):
         if obj.tzinfo:
@@ -292,13 +293,14 @@ cdef class DatetimeNoTzBinaryDumper(_BaseDatetimeDumper):
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
         delta = obj - pg_datetime_epoch
 
-        cdef int64_t micros = cdt.timedelta_microseconds(delta) + 1_000_000 * (
+        cdef int64_t us = cdt.timedelta_microseconds(delta) + 1_000_000 * (
             86_400 * <int64_t>cdt.timedelta_days(delta)
                 + <int64_t>cdt.timedelta_seconds(delta))
+        cdef uint64_t beus = endian.htobe64(us)
 
-        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(int64_t))
-        (<int64_t *>buf)[0] = endian.htobe64(micros)
-        return sizeof(int64_t)
+        cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus))
+        memcpy(buf, &beus, sizeof(beus))
+        return sizeof(beus)
 
 
 @cython.final
@@ -345,18 +347,22 @@ cdef class TimedeltaBinaryDumper(CDumper):
     oid = oids.INTERVAL_OID
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
-        cdef int64_t micros = (
+        cdef int64_t us = (
             1_000_000 * <int64_t>cdt.timedelta_seconds(obj)
             + cdt.timedelta_microseconds(obj))
+        cdef uint64_t beus = endian.htobe64(us)
+
         cdef int32_t days = cdt.timedelta_days(obj)
+        cdef uint32_t bedays = endian.htobe32(days)
 
+        # The third item is months
         cdef char *buf = CDumper.ensure_size(
-            rv, offset, sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t))
-        (<int64_t *>buf)[0] = endian.htobe64(micros)
-        (<int32_t *>(buf + sizeof(int64_t)))[0] = endian.htobe32(days)
-        (<int32_t *>(buf + sizeof(int64_t) + sizeof(int32_t)))[0] = 0
+            rv, offset, sizeof(beus) + sizeof(bedays) + sizeof(int32_t))
+        memcpy(buf, &beus, sizeof(beus))
+        memcpy(buf + sizeof(beus), &bedays, sizeof(bedays))
+        memset(buf + sizeof(beus) + sizeof(bedays), 0, sizeof(int32_t))
 
-        return sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t)
+        return sizeof(beus) + sizeof(bedays) + sizeof(int32_t)
 
 
 @cython.final
@@ -419,7 +425,9 @@ cdef class DateBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int days = endian.be32toh((<uint32_t *>data)[0])
+        cdef uint32_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef int days = endian.be32toh(bedata)
         cdef object pydays = days + PG_DATE_EPOCH_DAYS
         try:
             return PyObject_CallFunctionObjArgs(
@@ -467,7 +475,9 @@ cdef class TimeBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int64_t val = endian.be64toh((<uint64_t *>data)[0])
+        cdef uint64_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef int64_t val = endian.be64toh(bedata)
         cdef int h, m, s, us
 
         with cython.cdivision(True):
@@ -531,8 +541,14 @@ cdef class TimetzBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int64_t val = endian.be64toh((<uint64_t *>data)[0])
-        cdef int32_t off = endian.be32toh((<uint32_t *>(data + sizeof(int64_t)))[0])
+        cdef uint64_t beval
+        memcpy(&beval, data, sizeof(beval))
+        cdef int64_t val = endian.be64toh(beval)
+
+        cdef uint32_t beoff
+        memcpy(&beoff, data + sizeof(beval), sizeof(beoff))
+        cdef int32_t off = endian.be32toh(beoff)
+
         cdef int h, m, s, us
 
         with cython.cdivision(True):
@@ -664,7 +680,9 @@ cdef class TimestampBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int64_t val = endian.be64toh((<uint64_t *>data)[0])
+        cdef uint64_t beval
+        memcpy(&beval, data, sizeof(beval))
+        cdef int64_t val = endian.be64toh(beval)
         cdef int64_t micros, secs, days
 
         # Work only with positive values as the cdivision behaves differently
@@ -791,7 +809,9 @@ cdef class TimestamptzBinaryLoader(_BaseTimestamptzLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int64_t val = endian.be64toh((<uint64_t *>data)[0])
+        cdef uint64_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef int64_t val = endian.be64toh(bedata)
         cdef int64_t micros, secs, days
 
         # Work only with positive values as the cdivision behaves differently
@@ -951,11 +971,11 @@ cdef class IntervalBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef int64_t val = endian.be64toh((<uint64_t *>data)[0])
-        cdef int32_t days = endian.be32toh(
-            (<uint32_t *>(data + sizeof(int64_t)))[0])
-        cdef int32_t months = endian.be32toh(
-            (<uint32_t *>(data + sizeof(int64_t) + sizeof(int32_t)))[0])
+        cdef int32_t bedata[4]
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef int64_t val = endian.be64toh((<uint64_t *>bedata)[0])
+        cdef int32_t days = endian.be32toh(bedata[2])
+        cdef int32_t months = endian.be32toh(bedata[3])
 
         cdef int years
         with cython.cdivision(True):
index de76a236c4b39cbaa579a8ea75e6f3800782748d..f9580fd84a98a078b79e590283a8b4845090a1aa 100644 (file)
@@ -7,7 +7,7 @@ Cython adapters for numeric types.
 cimport cython
 
 from libc.stdint cimport *
-from libc.string cimport memcpy, strlen
+from libc.string cimport memcpy, memset, strlen
 from cpython.mem cimport PyMem_Free
 from cpython.dict cimport PyDict_GetItem, PyDict_SetItem
 from cpython.long cimport (
@@ -259,7 +259,9 @@ cdef class Int2BinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        return PyLong_FromLong(<int16_t>endian.be16toh((<uint16_t *>data)[0]))
+        cdef int16_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        return PyLong_FromLong(<int16_t>endian.be16toh(bedata))
 
 
 @cython.final
@@ -268,7 +270,9 @@ cdef class Int4BinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        return PyLong_FromLong(<int32_t>endian.be32toh((<uint32_t *>data)[0]))
+        cdef int32_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        return PyLong_FromLong(<int32_t>endian.be32toh(bedata))
 
 
 @cython.final
@@ -277,7 +281,9 @@ cdef class Int8BinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        return PyLong_FromLongLong(<int64_t>endian.be64toh((<uint64_t *>data)[0]))
+        cdef int64_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        return PyLong_FromLongLong(<int64_t>endian.be64toh(bedata))
 
 
 @cython.final
@@ -286,7 +292,9 @@ cdef class OidBinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        return PyLong_FromUnsignedLong(endian.be32toh((<uint32_t *>data)[0]))
+        cdef uint32_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        return PyLong_FromUnsignedLong(endian.be32toh(bedata))
 
 
 cdef class _FloatDumper(CDumper):
@@ -338,11 +346,11 @@ cdef class FloatBinaryDumper(CDumper):
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
         cdef double d = PyFloat_AsDouble(obj)
-        cdef uint64_t *intptr = <uint64_t *>&d
+        cdef uint64_t beval = endian.htobe64((<uint64_t *>&d)[0])
         cdef uint64_t *buf = <uint64_t *>CDumper.ensure_size(
-            rv, offset, sizeof(uint64_t))
-        buf[0] = endian.htobe64(intptr[0])
-        return sizeof(uint64_t)
+            rv, offset, sizeof(beval))
+        memcpy(buf, &beval, sizeof(beval))
+        return sizeof(beval)
 
 
 @cython.final
@@ -353,11 +361,11 @@ cdef class Float4BinaryDumper(CDumper):
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
         cdef float f = <float>PyFloat_AsDouble(obj)
-        cdef uint32_t *intptr = <uint32_t *>&f
+        cdef uint32_t beval = endian.htobe32((<uint32_t *>&f)[0])
         cdef uint32_t *buf = <uint32_t *>CDumper.ensure_size(
-            rv, offset, sizeof(uint32_t))
-        buf[0] = endian.htobe32(intptr[0])
-        return sizeof(uint32_t)
+            rv, offset, sizeof(beval))
+        memcpy(buf, &beval, sizeof(beval))
+        return sizeof(beval)
 
 
 @cython.final
@@ -378,7 +386,9 @@ cdef class Float4BinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef uint32_t asint = endian.be32toh((<uint32_t *>data)[0])
+        cdef uint32_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef uint32_t asint = endian.be32toh(bedata)
         # avoid warning:
         # dereferencing type-punned pointer will break strict-aliasing rules
         cdef char *swp = <char *>&asint
@@ -391,7 +401,9 @@ cdef class Float8BinaryLoader(CLoader):
     format = PQ_BINARY
 
     cdef object cload(self, const char *data, size_t length):
-        cdef uint64_t asint = endian.be64toh((<uint64_t *>data)[0])
+        cdef uint64_t bedata
+        memcpy(&bedata, data, sizeof(bedata))
+        cdef uint64_t asint = endian.be64toh(bedata)
         cdef char *swp = <char *>&asint
         return PyFloat_FromDouble((<double *>swp)[0])
 
@@ -448,24 +460,31 @@ cdef class NumericBinaryLoader(CLoader):
 
     cdef object cload(self, const char *data, size_t length):
 
-        cdef uint16_t *data16 = <uint16_t *>data
-        cdef uint16_t ndigits = endian.be16toh(data16[0])
-        cdef int16_t weight = <int16_t>endian.be16toh(data16[1])
-        cdef uint16_t sign = endian.be16toh(data16[2])
-        cdef uint16_t dscale = endian.be16toh(data16[3])
+        cdef uint16_t behead[4]
+        memcpy(&behead, data, sizeof(behead))
+        cdef uint16_t ndigits = endian.be16toh(behead[0])
+        cdef int16_t weight = <int16_t>endian.be16toh(behead[1])
+        cdef uint16_t sign = endian.be16toh(behead[2])
+        cdef uint16_t dscale = endian.be16toh(behead[3])
+
         cdef int shift
         cdef int i
         cdef PyObject *pctx
         cdef object key
+        cdef char *digitptr
+        cdef uint16_t bedigit
 
         if sign == NUMERIC_POS or sign == NUMERIC_NEG:
             if length != (4 + ndigits) * sizeof(uint16_t):
                 raise e.DataError("bad ndigits in numeric binary representation")
 
             val = 0
+            digitptr = data + sizeof(behead)
             for i in range(ndigits):
+                memcpy(&bedigit, digitptr, sizeof(bedigit))
+                digitptr += sizeof(bedigit)
                 val *= 10_000
-                val += endian.be16toh(data16[i + 4])
+                val += endian.be16toh(bedigit)
 
             shift = dscale - (ndigits - weight - 1) * DEC_DIGITS
 
@@ -595,28 +614,30 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary(
     cdef int sign = t[0]
     cdef tuple digits = t[1]
     cdef uint16_t *buf
+    cdef uint16_t behead[4]
     cdef Py_ssize_t length
 
     cdef object pyexp = t[2]
     cdef const char *bexp
+
     if not isinstance(pyexp, int):
         # Handle inf, nan
-        length = 4 * sizeof(uint16_t)
-        buf = <uint16_t *>CDumper.ensure_size(rv, offset, length)
-        buf[0] = 0
-        buf[1] = 0
-        buf[3] = 0
+        buf = <uint16_t *>CDumper.ensure_size(rv, offset, sizeof(behead))
+        behead[0] = 0
+        behead[1] = 0
+        behead[3] = 0
         bexp = PyUnicode_AsUTF8(pyexp)
         if bexp[0] == b'n' or bexp[0] == b'N':
-            buf[2] = endian.htobe16(NUMERIC_NAN)
+            behead[2] = endian.htobe16(NUMERIC_NAN)
         elif bexp[0] == b'F':
             if sign:
-                buf[2] = endian.htobe16(NUMERIC_NINF)
+                behead[2] = endian.htobe16(NUMERIC_NINF)
             else:
-                buf[2] = endian.htobe16(NUMERIC_PINF)
+                behead[2] = endian.htobe16(NUMERIC_PINF)
         else:
             raise e.DataError(f"unexpected decimal exponent: {pyexp}")
-        return length
+        memcpy(buf, behead, sizeof(behead))
+        return sizeof(behead)
 
     cdef int exp = pyexp
     cdef uint16_t ndigits = <uint16_t>len(digits)
@@ -635,13 +656,13 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary(
         ndigits += exp % DEC_DIGITS
 
     if nzdigits == 0:
-        length = 4 * sizeof(uint16_t)
-        buf = <uint16_t *>CDumper.ensure_size(rv, offset, length)
-        buf[0] = 0  # ndigits
-        buf[1] = 0  # weight
-        buf[2] = endian.htobe16(NUMERIC_POS)  # sign
-        buf[3] = endian.htobe16(dscale)
-        return length
+        buf = <uint16_t *>CDumper.ensure_size(rv, offset, sizeof(behead))
+        behead[0] = 0  # ndigits
+        behead[1] = 0  # weight
+        behead[2] = endian.htobe16(NUMERIC_POS)  # sign
+        behead[3] = endian.htobe16(dscale)
+        memcpy(buf, behead, sizeof(behead))
+        return sizeof(behead)
 
     # Equivalent of 0-padding left to align the py digits to the pg digits
     # but without changing the digits tuple.
@@ -656,25 +677,28 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary(
 
     cdef int tmp = nzdigits + wi
     cdef int pgdigits = tmp // DEC_DIGITS + (tmp % DEC_DIGITS and 1)
-    length = (pgdigits + 4) * sizeof(uint16_t)
+    length = sizeof(behead) + pgdigits * sizeof(uint16_t)
     buf = <uint16_t*>CDumper.ensure_size(rv, offset, length)
-    buf[0] = endian.htobe16(pgdigits)
-    buf[1] = endian.htobe16(<int16_t>((ndigits + exp) // DEC_DIGITS - 1))
-    buf[2] = endian.htobe16(NUMERIC_NEG) if sign else endian.htobe16(NUMERIC_POS)
-    buf[3] = endian.htobe16(dscale)
-
-    cdef uint16_t pgdigit = 0
-    cdef int bi = 4
+    behead[0] = endian.htobe16(pgdigits)
+    behead[1] = endian.htobe16(<int16_t>((ndigits + exp) // DEC_DIGITS - 1))
+    behead[2] = endian.htobe16(NUMERIC_NEG) if sign else endian.htobe16(NUMERIC_POS)
+    behead[3] = endian.htobe16(dscale)
+    memcpy(buf, behead, sizeof(behead))
+    buf += 4
+
+    cdef uint16_t pgdigit = 0, bedigit
     for i in range(nzdigits):
         pgdigit += pydigit_weights[wi] * <int>(digits[i])
         wi += 1
         if wi >= DEC_DIGITS:
-            buf[bi] = endian.htobe16(pgdigit)
+            bedigit = endian.htobe16(pgdigit)
+            memcpy(buf, &bedigit, sizeof(bedigit))
+            buf += 1
             pgdigit = wi = 0
-            bi += 1
 
     if pgdigit:
-        buf[bi] = endian.htobe16(pgdigit)
+        bedigit = endian.htobe16(pgdigit)
+        memcpy(buf, &bedigit, sizeof(bedigit))
 
     return length
 
@@ -729,37 +753,31 @@ cdef Py_ssize_t dump_int_or_sub_to_text(
 cdef Py_ssize_t dump_int_to_int2_binary(
     obj, bytearray rv, Py_ssize_t offset
 ) except -1:
-    cdef int16_t *buf = <int16_t *>CDumper.ensure_size(
-        rv, offset, sizeof(int16_t))
     cdef int16_t val = <int16_t>PyLong_AsLongLong(obj)
-    # swap bytes if needed
-    cdef uint16_t *ptvar = <uint16_t *>(&val)
-    buf[0] = endian.htobe16(ptvar[0])
-    return sizeof(int16_t)
+    cdef int16_t *buf = <int16_t *>CDumper.ensure_size(rv, offset, sizeof(obj))
+    cdef uint16_t beval = endian.htobe16(val)  # swap bytes if needed
+    memcpy(buf, &beval, sizeof(beval))
+    return sizeof(val)
 
 
 cdef Py_ssize_t dump_int_to_int4_binary(
     obj, bytearray rv, Py_ssize_t offset
 ) except -1:
-    cdef int32_t *buf = <int32_t *>CDumper.ensure_size(
-        rv, offset, sizeof(int32_t))
     cdef int32_t val = <int32_t>PyLong_AsLongLong(obj)
-    # swap bytes if needed
-    cdef uint32_t *ptvar = <uint32_t *>(&val)
-    buf[0] = endian.htobe32(ptvar[0])
-    return sizeof(int32_t)
+    cdef int32_t *buf = <int32_t *>CDumper.ensure_size(rv, offset, sizeof(val))
+    cdef uint32_t beval = endian.htobe32(val)  # swap bytes if needed
+    memcpy(buf, &beval, sizeof(beval))
+    return sizeof(val)
 
 
 cdef Py_ssize_t dump_int_to_int8_binary(
     obj, bytearray rv, Py_ssize_t offset
 ) except -1:
-    cdef int64_t *buf = <int64_t *>CDumper.ensure_size(
-        rv, offset, sizeof(int64_t))
     cdef int64_t val = PyLong_AsLongLong(obj)
-    # swap bytes if needed
-    cdef uint64_t *ptvar = <uint64_t *>(&val)
-    buf[0] = endian.htobe64(ptvar[0])
-    return sizeof(int64_t)
+    cdef int64_t *buf = <int64_t *>CDumper.ensure_size(rv, offset, sizeof(val))
+    cdef uint64_t beval = endian.htobe64(val)  # swap bytes if needed
+    memcpy(buf, &beval, sizeof(beval))
+    return sizeof(val)
 
 
 cdef Py_ssize_t dump_int_to_numeric_binary(obj, bytearray rv, Py_ssize_t offset) except -1:
@@ -775,20 +793,24 @@ cdef Py_ssize_t dump_int_to_numeric_binary(obj, bytearray rv, Py_ssize_t offset)
     cdef Py_ssize_t length = sizeof(uint16_t) * (ndigits + 4)
     cdef uint16_t *buf
     buf = <uint16_t *><void *>CDumper.ensure_size(rv, offset, length)
-    buf[0] = endian.htobe16(ndigits)
-    buf[1] = endian.htobe16(ndigits - 1)  # weight
-    buf[2] = endian.htobe16(sign)
-    buf[3] = 0  # dscale
+
+    cdef uint16_t behead[4]
+    behead[0] = endian.htobe16(ndigits)
+    behead[1] = endian.htobe16(ndigits - 1)  # weight
+    behead[2] = endian.htobe16(sign)
+    behead[3] = 0  # dscale
+    memcpy(buf, behead, sizeof(behead))
 
     cdef int i = 4 + ndigits - 1
-    cdef uint16_t rem
+    cdef uint16_t rem, berem
     while obj:
         rem = obj % 10000
         obj //= 10000
-        buf[i] = endian.htobe16(rem)
+        berem = endian.htobe16(rem)
+        memcpy(buf + i, &berem, sizeof(berem))
         i -= 1
     while i > 3:
-        buf[i] = 0
+        memset(buf + i, 0, sizeof(buf[0]))
         i -= 1
 
     return length