Skip to content

Commit a5fbb8f

Browse files
committed
Fix handling of OIDs >= 2**31
Currently asyncpg (incorrectly) assumes OIDs to be signed 32-bit integers, whereas in reality they are unsigned. As a result, things would crash once the OID sequence reaches 2**31. Fix this by decoding OID values as unsigned longs. Fixes: #279
1 parent 3809c5b commit a5fbb8f

File tree

10 files changed

+157
-42
lines changed

10 files changed

+157
-42
lines changed

asyncpg/cluster.py

+36
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,42 @@ def get_connection_spec(self):
300300
def override_connection_spec(self, **kwargs):
301301
self._connection_spec_override = kwargs
302302

303+
def reset_wal(self, *, oid=None, xid=None):
304+
status = self.get_status()
305+
if status == 'not-initialized':
306+
raise ClusterError(
307+
'cannot modify WAL status: cluster is not initialized')
308+
309+
if status == 'running':
310+
raise ClusterError(
311+
'cannot modify WAL status: cluster is running')
312+
313+
opts = []
314+
if oid is not None:
315+
opts.extend(['-o', str(oid)])
316+
if xid is not None:
317+
opts.extend(['-x', str(xid)])
318+
if not opts:
319+
return
320+
321+
opts.append(self._data_dir)
322+
323+
try:
324+
reset_wal = self._find_pg_binary('pg_resetwal')
325+
except ClusterError:
326+
reset_wal = self._find_pg_binary('pg_resetxlog')
327+
328+
process = subprocess.run(
329+
[reset_wal] + opts,
330+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
331+
332+
stderr = process.stderr
333+
334+
if process.returncode != 0:
335+
raise ClusterError(
336+
'pg_resetwal exited with status {:d}: {}'.format(
337+
process.returncode, stderr.decode()))
338+
303339
def reset_hba(self):
304340
"""Remove all records from pg_hba.conf."""
305341
status = self.get_status()

asyncpg/protocol/buffer.pxd

+3-3
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ cdef class ReadBuffer:
9797
cdef feed_data(self, data)
9898
cdef inline _ensure_first_buf(self)
9999
cdef _switch_to_next_buf(self)
100-
cdef inline read_byte(self)
100+
cdef inline char read_byte(self) except? -1
101101
cdef inline const char* _try_read_bytes(self, ssize_t nbytes)
102102
cdef inline _read(self, char *buf, ssize_t nbytes)
103103
cdef read(self, ssize_t nbytes)
104104
cdef inline const char* read_bytes(self, ssize_t n) except NULL
105-
cdef inline read_int32(self)
106-
cdef inline read_int16(self)
105+
cdef inline int32_t read_int32(self) except? -1
106+
cdef inline int16_t read_int16(self) except? -1
107107
cdef inline read_cstr(self)
108108
cdef int32_t has_message(self) except -1
109109
cdef inline int32_t has_message_type(self, char mtype) except -1

asyncpg/protocol/buffer.pyx

+3-3
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ cdef class ReadBuffer:
376376

377377
return Memory.new(buf, result, nbytes)
378378

379-
cdef inline read_byte(self):
379+
cdef inline char read_byte(self) except? -1:
380380
cdef const char *first_byte
381381

382382
if ASYNCPG_DEBUG:
@@ -404,7 +404,7 @@ cdef class ReadBuffer:
404404
mem = <Memory>(self.read(n))
405405
return mem.buf
406406

407-
cdef inline read_int32(self):
407+
cdef inline int32_t read_int32(self) except? -1:
408408
cdef:
409409
Memory mem
410410
const char *cbuf
@@ -417,7 +417,7 @@ cdef class ReadBuffer:
417417
mem = <Memory>(self.read(4))
418418
return hton.unpack_int32(mem.buf)
419419

420-
cdef inline read_int16(self):
420+
cdef inline int16_t read_int16(self) except? -1:
421421
cdef:
422422
Memory mem
423423
const char *cbuf

asyncpg/protocol/codecs/base.pyx

+23-7
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,22 @@ cdef codec_decode_func_ex(ConnectionSettings settings, FastReadBuffer buf,
373373
return (<Codec>arg).decode(settings, buf)
374374

375375

376+
cdef uint32_t pylong_as_oid(val) except? 0xFFFFFFFFl:
377+
cdef:
378+
int64_t oid = 0
379+
bint overflow = False
380+
381+
try:
382+
oid = cpython.PyLong_AsLongLong(val)
383+
except OverflowError:
384+
overflow = True
385+
386+
if overflow or (oid < 0 or oid > UINT32_MAX):
387+
raise OverflowError('OID value too large: {!r}'.format(val))
388+
389+
return <uint32_t>val
390+
391+
376392
cdef class DataCodecConfig:
377393
def __init__(self, cache_key):
378394
try:
@@ -523,9 +539,10 @@ cdef class DataCodecConfig:
523539
Codec core_codec
524540
encode_func c_encoder = NULL
525541
decode_func c_decoder = NULL
542+
uint32_t oid = pylong_as_oid(typeoid)
526543

527544
if xformat == PG_XFORMAT_TUPLE:
528-
core_codec = get_any_core_codec(typeoid, format, xformat)
545+
core_codec = get_any_core_codec(oid, format, xformat)
529546
if core_codec is None:
530547
raise ValueError(
531548
"{} type does not support 'tuple' exchange format".format(
@@ -538,7 +555,7 @@ cdef class DataCodecConfig:
538555
self.remove_python_codec(typeoid, typename, typeschema)
539556

540557
self._local_type_codecs[typeoid] = \
541-
Codec.new_python_codec(typeoid, typename, typeschema, typekind,
558+
Codec.new_python_codec(oid, typename, typeschema, typekind,
542559
encoder, decoder, c_encoder, c_decoder,
543560
format, xformat)
544561

@@ -551,19 +568,18 @@ cdef class DataCodecConfig:
551568
cdef:
552569
Codec codec
553570
Codec target_codec
571+
uint32_t oid = pylong_as_oid(typeoid)
572+
uint32_t alias_pid
554573

555574
if format == PG_FORMAT_ANY:
556575
formats = (PG_FORMAT_BINARY, PG_FORMAT_TEXT)
557576
else:
558577
formats = (format,)
559578

560579
for format in formats:
561-
if self.get_codec(typeoid, format) is not None:
562-
raise ValueError('cannot override codec for type {}'.format(
563-
typeoid))
564-
565580
if isinstance(alias_to, int):
566-
target_codec = self.get_codec(alias_to, format)
581+
alias_oid = pylong_as_oid(alias_to)
582+
target_codec = self.get_codec(alias_oid, format)
567583
else:
568584
target_codec = get_extra_codec(alias_to, format)
569585

asyncpg/protocol/codecs/int.pyx

+25-8
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ cdef int2_encode(ConnectionSettings settings, WriteBuffer buf, obj):
2727
except OverflowError:
2828
overflow = 1
2929

30-
if overflow or val < -32768 or val > 32767:
30+
if overflow or val < INT16_MIN or val > INT16_MAX:
3131
raise OverflowError(
32-
'int too big to be encoded as INT2: {!r}'.format(obj))
32+
'int16 value out of range: {!r}'.format(obj))
3333

3434
buf.write_int32(2)
3535
buf.write_int16(<int16_t>val)
@@ -49,10 +49,9 @@ cdef int4_encode(ConnectionSettings settings, WriteBuffer buf, obj):
4949
overflow = 1
5050

5151
# "long" and "long long" have the same size for x86_64, need an extra check
52-
if overflow or (sizeof(val) > 4 and (val < -2147483648 or
53-
val > 2147483647)):
52+
if overflow or (sizeof(val) > 4 and (val < INT32_MIN or val > INT32_MAX)):
5453
raise OverflowError(
55-
'int too big to be encoded as INT4: {!r}'.format(obj))
54+
'int32 value out of range: {!r}'.format(obj))
5655

5756
buf.write_int32(4)
5857
buf.write_int32(<int32_t>val)
@@ -62,6 +61,25 @@ cdef int4_decode(ConnectionSettings settings, FastReadBuffer buf):
6261
return cpython.PyLong_FromLong(hton.unpack_int32(buf.read(4)))
6362

6463

64+
cdef uint4_encode(ConnectionSettings settings, WriteBuffer buf, obj):
65+
cdef unsigned long val = 0
66+
67+
val = cpython.PyLong_AsUnsignedLong(obj)
68+
69+
# "long" and "long long" have the same size for x86_64, need an extra check
70+
if sizeof(val) > 4 and val > UINT32_MAX:
71+
raise OverflowError(
72+
'uint32 value out of range: {!r}'.format(obj))
73+
74+
buf.write_int32(4)
75+
buf.write_int32(<int32_t>val)
76+
77+
78+
cdef uint4_decode(ConnectionSettings settings, FastReadBuffer buf):
79+
return cpython.PyLong_FromUnsignedLong(
80+
<uint32_t>hton.unpack_int32(buf.read(4)))
81+
82+
6583
cdef int8_encode(ConnectionSettings settings, WriteBuffer buf, obj):
6684
cdef int overflow = 0
6785
cdef long long val
@@ -72,10 +90,9 @@ cdef int8_encode(ConnectionSettings settings, WriteBuffer buf, obj):
7290
overflow = 1
7391

7492
# Just in case for systems with "long long" bigger than 8 bytes
75-
if overflow or (sizeof(val) > 8 and (val < -9223372036854775808 or
76-
val > 9223372036854775807)):
93+
if overflow or (sizeof(val) > 8 and (val < INT64_MIN or val > INT64_MAX)):
7794
raise OverflowError(
78-
'int too big to be encoded as INT8: {!r}'.format(obj))
95+
'int64 value out of range: {!r}'.format(obj))
7996

8097
buf.write_int32(8)
8198
buf.write_int64(<int64_t>val)

asyncpg/protocol/codecs/misc.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ cdef init_pseudo_codecs():
3636

3737
for oid_type in oid_types:
3838
register_core_codec(oid_type,
39-
<encode_func>&int4_encode,
40-
<decode_func>&int4_decode,
39+
<encode_func>&uint4_encode,
40+
<decode_func>&uint4_decode,
4141
PG_FORMAT_BINARY)
4242

4343
# reg* types -- these are really system catalog OIDs, but

asyncpg/protocol/codecs/tid.pyx

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ cdef tid_encode(ConnectionSettings settings, WriteBuffer buf, obj):
2323
overflow = 1
2424

2525
# "long" and "long long" have the same size for x86_64, need an extra check
26-
if overflow or (sizeof(block) > 4 and block > 4294967295):
26+
if overflow or (sizeof(block) > 4 and block > UINT32_MAX):
2727
raise OverflowError(
28-
'block too big to be encoded as UINT4: {!r}'.format(obj[0]))
28+
'tuple id block value out of range: {!r}'.format(obj[0]))
2929

3030
try:
3131
offset = cpython.PyLong_AsUnsignedLong(obj[1])
@@ -35,7 +35,7 @@ cdef tid_encode(ConnectionSettings settings, WriteBuffer buf, obj):
3535

3636
if overflow or offset > 65535:
3737
raise OverflowError(
38-
'offset too big to be encoded as UINT2: {!r}'.format(obj[1]))
38+
'tuple id offset value out of range: {!r}'.format(obj[1]))
3939

4040
buf.write_int32(6)
4141
buf.write_int32(<int32_t>block)

asyncpg/protocol/prepared_stmt.pyx

+10-10
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ cdef class PreparedStatementState:
163163
list cols_names
164164
object cols_mapping
165165
tuple row
166-
int oid
166+
uint32_t oid
167167
Codec codec
168168
list codecs
169169

@@ -183,7 +183,7 @@ cdef class PreparedStatementState:
183183
cols_mapping[col_name] = i
184184
cols_names.append(col_name)
185185
oid = row[3]
186-
codec = self.settings.get_data_codec(<uint32_t>oid)
186+
codec = self.settings.get_data_codec(oid)
187187
if codec is None or not codec.has_decoder():
188188
raise RuntimeError('no decoder for OID {}'.format(oid))
189189
if not codec.is_binary():
@@ -198,7 +198,7 @@ cdef class PreparedStatementState:
198198

199199
cdef _ensure_args_encoder(self):
200200
cdef:
201-
int p_oid
201+
uint32_t p_oid
202202
Codec codec
203203
list codecs = []
204204

@@ -207,7 +207,7 @@ cdef class PreparedStatementState:
207207

208208
for i from 0 <= i < self.args_num:
209209
p_oid = self.parameters_desc[i]
210-
codec = self.settings.get_data_codec(<uint32_t>p_oid)
210+
codec = self.settings.get_data_codec(p_oid)
211211
if codec is None or not codec.has_encoder():
212212
raise RuntimeError('no encoder for OID {}'.format(p_oid))
213213
if codec.type not in {}:
@@ -290,14 +290,14 @@ cdef _decode_parameters_desc(object desc):
290290
cdef:
291291
ReadBuffer reader
292292
int16_t nparams
293-
int32_t p_oid
293+
uint32_t p_oid
294294
list result = []
295295

296296
reader = ReadBuffer.new_message_parser(desc)
297297
nparams = reader.read_int16()
298298

299299
for i from 0 <= i < nparams:
300-
p_oid = reader.read_int32()
300+
p_oid = <uint32_t>reader.read_int32()
301301
result.append(p_oid)
302302

303303
return result
@@ -310,9 +310,9 @@ cdef _decode_row_desc(object desc):
310310
int16_t nfields
311311

312312
bytes f_name
313-
int32_t f_table_oid
313+
uint32_t f_table_oid
314314
int16_t f_column_num
315-
int32_t f_dt_oid
315+
uint32_t f_dt_oid
316316
int16_t f_dt_size
317317
int32_t f_dt_mod
318318
int16_t f_format
@@ -325,9 +325,9 @@ cdef _decode_row_desc(object desc):
325325

326326
for i from 0 <= i < nfields:
327327
f_name = reader.read_cstr()
328-
f_table_oid = reader.read_int32()
328+
f_table_oid = <uint32_t>reader.read_int32()
329329
f_column_num = reader.read_int16()
330-
f_dt_oid = reader.read_int32()
330+
f_dt_oid = <uint32_t>reader.read_int32()
331331
f_dt_size = reader.read_int16()
332332
f_dt_mod = reader.read_int32()
333333
f_format = reader.read_int16()

asyncpg/protocol/protocol.pyx

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import socket
1818
import time
1919

2020
from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, \
21-
int32_t, uint32_t, int64_t, uint64_t
21+
int32_t, uint32_t, int64_t, uint64_t, \
22+
INT16_MIN, INT16_MAX, INT32_MIN, INT32_MAX, \
23+
UINT32_MAX, INT64_MIN, INT64_MAX
2224

2325
from asyncpg.protocol cimport record
2426

0 commit comments

Comments
 (0)