Skip to content

Commit fd6ffc8

Browse files
committed
Hash serialization: Use actual type alignments when parsing specs.
Rather than, for instance, assuming __alignof__(uint64_t) == 8.
1 parent cb7a784 commit fd6ffc8

File tree

2 files changed

+35
-15
lines changed

2 files changed

+35
-15
lines changed

ext/hash/hash.c

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@
3232

3333
#include "hash_arginfo.h"
3434

35+
#ifdef PHP_WIN32
36+
# define __alignof__ __alignof
37+
#else
38+
# ifndef HAVE_ALIGNOF
39+
# include <stddef.h>
40+
# define __alignof__(type) offsetof (struct { char c; type member;}, member)
41+
# endif
42+
#endif
43+
3544
HashTable php_hash_hashtable;
3645
zend_class_entry *php_hashcontext_ce;
3746
static zend_object_handlers php_hashcontext_handlers;
@@ -115,20 +124,36 @@ PHP_HASH_API int php_hash_copy(const void *ops, void *orig_context, void *dest_c
115124
/* }}} */
116125

117126

118-
static size_t parse_serialize_spec(const char **specp, size_t *pos, size_t *sz) {
119-
size_t count;
127+
static inline size_t align_to(size_t pos, size_t alignment) {
128+
size_t offset = pos & (alignment - 1);
129+
return pos + (offset ? alignment - offset : 0);
130+
}
131+
132+
static size_t parse_serialize_spec(const char **specp, size_t *pos, size_t *sz,
133+
size_t *max_alignment) {
134+
size_t count, alignment;
120135
const char *spec = *specp;
136+
/* parse size */
121137
if (*spec == 's') {
122138
*sz = 2;
139+
alignment = __alignof__(uint16_t); /* usually 2 */
123140
} else if (*spec == 'l') {
124141
*sz = 4;
142+
alignment = __alignof__(uint32_t); /* usually 4 */
125143
} else if (*spec == 'q') {
126144
*sz = 8;
145+
alignment = __alignof__(uint64_t); /* usually 8 */
127146
} else if (*spec == 'i') {
128147
*sz = sizeof(int);
148+
alignment = __alignof__(int); /* usually 4 */
129149
} else {
130150
*sz = 1;
151+
alignment = 1;
131152
}
153+
/* process alignment */
154+
*pos = align_to(*pos, alignment);
155+
*max_alignment = *max_alignment < alignment ? alignment : *max_alignment;
156+
/* parse count */
132157
++spec;
133158
if (isdigit((unsigned char) *spec)) {
134159
count = 0;
@@ -140,10 +165,6 @@ static size_t parse_serialize_spec(const char **specp, size_t *pos, size_t *sz)
140165
count = 1;
141166
}
142167
*specp = spec;
143-
// alignment
144-
if (*sz > 1 && (*pos & (*sz - 1)) != 0) {
145-
*pos += *sz - (*pos & (*sz - 1));
146-
}
147168
return count;
148169
}
149170

@@ -184,6 +205,7 @@ static void one_to_buffer(size_t sz, unsigned char *buf, uint64_t val) {
184205
l[COUNT] -- serialize COUNT 32-bit integers
185206
q[COUNT] -- serialize COUNT 64-bit integers
186207
i[COUNT] -- serialize COUNT `int`s
208+
-[COUNT] -- skip COUNT bytes
187209
. (must be last character) -- assert that the hash context has exactly
188210
this size
189211
Example: "llllllb64l16." is the spec for an MD5 context: 6 32-bit
@@ -198,14 +220,14 @@ static void one_to_buffer(size_t sz, unsigned char *buf, uint64_t val) {
198220

199221
PHP_HASH_API int php_hash_serialize_spec(const php_hashcontext_object *hash, zend_long *magic, zval *zv, const char *spec) /* {{{ */
200222
{
201-
size_t pos = 0, sz, count;
223+
size_t pos = 0, max_alignment = 1, sz, count;
202224
unsigned char *buf = (unsigned char *) hash->context;
203225
zval tmp;
204226
*magic = PHP_HASH_SERIALIZE_MAGIC_SPEC;
205227
array_init(zv);
206228
while (*spec != '\0' && *spec != '.') {
207229
char specch = *spec;
208-
count = parse_serialize_spec(&spec, &pos, &sz);
230+
count = parse_serialize_spec(&spec, &pos, &sz, &max_alignment);
209231
if (pos + count * sz > hash->ops->context_size) {
210232
return FAILURE;
211233
}
@@ -229,7 +251,7 @@ PHP_HASH_API int php_hash_serialize_spec(const php_hashcontext_object *hash, zen
229251
}
230252
}
231253
}
232-
if (*spec == '.' && pos != hash->ops->context_size) {
254+
if (*spec == '.' && align_to(pos, max_alignment) != hash->ops->context_size) {
233255
return FAILURE;
234256
}
235257
return SUCCESS;
@@ -244,15 +266,15 @@ PHP_HASH_API int php_hash_serialize_spec(const php_hashcontext_object *hash, zen
244266

245267
PHP_HASH_API int php_hash_unserialize_spec(php_hashcontext_object *hash, zend_long magic, const zval *zv, const char *spec) /* {{{ */
246268
{
247-
size_t pos = 0, sz, count, j = 0;
269+
size_t pos = 0, max_alignment = 1, sz, count, j = 0;
248270
unsigned char *buf = (unsigned char *) hash->context;
249271
zval *elt;
250272
if (magic != PHP_HASH_SERIALIZE_MAGIC_SPEC || Z_TYPE_P(zv) != IS_ARRAY) {
251273
return FAILURE;
252274
}
253275
while (*spec != '\0' && *spec != '.') {
254276
char specch = *spec;
255-
count = parse_serialize_spec(&spec, &pos, &sz);
277+
count = parse_serialize_spec(&spec, &pos, &sz, &max_alignment);
256278
if (pos + count * sz > hash->ops->context_size) {
257279
return -999;
258280
}
@@ -289,7 +311,7 @@ PHP_HASH_API int php_hash_unserialize_spec(php_hashcontext_object *hash, zend_lo
289311
}
290312
}
291313
}
292-
if (*spec == '.' && pos != hash->ops->context_size) {
314+
if (*spec == '.' && align_to(pos, max_alignment) != hash->ops->context_size) {
293315
return -999;
294316
}
295317
return SUCCESS;

ext/hash/php_hash.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,8 @@ PHP_HASH_API int php_hash_serialize(const php_hashcontext_object *context, zend_
152152
PHP_HASH_API int php_hash_unserialize(php_hashcontext_object *context, zend_long magic, const zval *zv);
153153

154154
static inline void *php_hash_alloc_context(const php_hash_ops *ops) {
155-
void *context = emalloc(ops->context_size);
156155
/* Zero out context memory so serialization doesn't expose internals */
157-
memset(context, 0, ops->context_size);
158-
return context;
156+
return ecalloc(1, ops->context_size);
159157
}
160158

161159
static inline void php_hash_bin2hex(char *out, const unsigned char *in, size_t in_len)

0 commit comments

Comments
 (0)