# 15: . STOP
self.assertEqual(self.loads(pickled), 42)
+ def test_frame_ends_at_opcode_boundary(self):
+ # A frame may end exactly between two opcodes; the following opcodes
+ # are then read from outside the frame.
+ for pickled in [
+ b'\x80\x04\x95\x01\x00\x00\x00\x00\x00\x00\x00N.', # FRAME 1, NONE, STOP
+ b'\x80\x04\x95\x00\x00\x00\x00\x00\x00\x00\x00N.', # empty FRAME, NONE, STOP
+ ]:
+ with self.subTest(pickled=pickled):
+ self.assertIsNone(self.loads(pickled))
+
+ def test_frame_does_not_straddle_boundary(self):
+ # An opcode or its argument must not cross a frame boundary
+ # (PEP 3154). Such a pickle must be rejected rather than silently
+ # reading past the declared frame length, which would make the
+ # meaning of the pickle diverge from its pickletools disassembly.
+ # See gh-154848.
+ for pickled in [
+ # FRAME 6; UNICODE argument read by readline() straddles the frame.
+ b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00Vhelloworld\n.',
+ # FRAME 6; BINUNICODE argument straddles the frame.
+ b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00'
+ b'X\x0a\x00\x00\x00helloworld.',
+ # FRAME 3; SHORT_BINBYTES argument straddles the frame.
+ b'\x80\x04\x95\x03\x00\x00\x00\x00\x00\x00\x00C\x0ahelloworld.',
+ # FRAME 9; GLOBAL argument (second line) straddles the frame.
+ b'\x80\x04\x95\x09\x00\x00\x00\x00\x00\x00\x00cbuiltins\nprint\n.',
+ ]:
+ self.check_unpickling_error(self.truncated_errors, pickled)
+
+ def test_nested_frame(self):
+ # A new frame must not begin before the current one has ended: here
+ # the outer frame still has data left after the inner frame header.
+ pickled = (b'\x80\x04\x95\x0c\x00\x00\x00\x00\x00\x00\x00'
+ b'N\x95\x00\x00\x00\x00\x00\x00\x00\x00NN.')
+ self.check_unpickling_error(self.truncated_errors, pickled)
+
+ # But the inner frame header may lie inside the outer frame as long as
+ # it exactly consumes it.
+ pickled = (b'\x80\x04\x95\x0a\x00\x00\x00\x00\x00\x00\x00'
+ b'N\x95\x00\x00\x00\x00\x00\x00\x00\x00N.')
+ self.assertIsNone(self.loads(pickled))
+
def test_compat_unpickle(self):
# xrange(1, 7)
pickled = b'\x80\x02c__builtin__\nxrange\nK\x01K\x07K\x01\x87R.'
Py_ssize_t input_len;
Py_ssize_t next_read_idx;
Py_ssize_t prefetched_idx; /* index of first prefetched byte */
+ Py_ssize_t frame_end; /* End of the current frame, or -1 if not in a
+ frame. While in a frame, input_len is capped
+ here so reads can't cross it (PEP 3154). */
+ Py_ssize_t saved_input_len; /* input_len to restore when the frame ends. */
PyObject *read; /* read() method of the input stream. */
PyObject *readinto; /* readinto() method of the input stream. */
self->input_len = self->buffer.len;
self->next_read_idx = 0;
self->prefetched_idx = self->input_len;
+ self->frame_end = -1;
return self->input_len;
}
return -1;
}
+/* End the current frame, uncapping input_len. The frame must be fully read. */
+static void
+_Unpickler_EndFrame(UnpicklerObject *self)
+{
+ assert(self->frame_end >= 0);
+ assert(self->next_read_idx == self->frame_end);
+ self->input_len = self->saved_input_len;
+ self->frame_end = -1;
+}
+
+/* Reached the end of the current frame. If straddle, the read crosses the
+ frame boundary (PEP 3154) and fails; otherwise end the frame. */
+static int
+_Unpickler_LeaveFrame(PickleState *st, UnpicklerObject *self, int straddle)
+{
+ if (straddle) {
+ PyErr_SetString(st->UnpicklingError,
+ "pickle exhausted before end of frame");
+ return -1;
+ }
+ _Unpickler_EndFrame(self);
+ return 0;
+}
+
/* Skip any consumed data that was only prefetched using peek() */
static int
_Unpickler_SkipConsumed(UnpicklerObject *self)
return -1;
}
+ if (self->frame_end >= 0) {
+ if (_Unpickler_LeaveFrame(st, self,
+ self->next_read_idx < self->frame_end) < 0) {
+ return -1;
+ }
+ /* Frame ended; the read may now be satisfied from the buffer. */
+ if (n <= self->input_len - self->next_read_idx) {
+ *s = self->input_buffer + self->next_read_idx;
+ self->next_read_idx += n;
+ return n;
+ }
+ }
+
/* This case is handled by the _Unpickler_Read() macro for efficiency */
assert(self->next_read_idx + n > self->input_len);
}
}
+ if (self->frame_end >= 0) {
+ /* in_buffer > 0 is next_read_idx < frame_end on entry: frame data was
+ consumed above, so the read crosses the frame boundary. */
+ if (_Unpickler_LeaveFrame(state, self, in_buffer > 0) < 0) {
+ return -1;
+ }
+ in_buffer = self->input_len - self->next_read_idx;
+ if (in_buffer > 0) {
+ Py_ssize_t to_read = Py_MIN(in_buffer, n);
+ memcpy(buf, self->input_buffer + self->next_read_idx, to_read);
+ self->next_read_idx += to_read;
+ buf += to_read;
+ n -= to_read;
+ if (n == 0) {
+ return n;
+ }
+ }
+ }
+
/* Read from file */
if (!self->read) {
/* We're unpickling memory, this means the input is truncated */
{
Py_ssize_t i, num_read;
+rescan:
for (i = self->next_read_idx; i < self->input_len; i++) {
if (self->input_buffer[i] == '\n') {
char *line_start = self->input_buffer + self->next_read_idx;
return _Unpickler_CopyLine(self, line_start, num_read, result);
}
}
+ if (self->frame_end >= 0) {
+ if (_Unpickler_LeaveFrame(state, self,
+ self->next_read_idx < self->frame_end) < 0) {
+ return -1;
+ }
+ /* Frame ended; continue the line past its end. */
+ goto rescan;
+ }
if (!self->read)
return bad_readline(state);
self->input_len = 0;
self->next_read_idx = 0;
self->prefetched_idx = 0;
+ self->frame_end = -1;
+ self->saved_input_len = 0;
self->read = NULL;
self->readinto = NULL;
self->readline = NULL;
if (_Unpickler_Read(self, state, &s, 8) < 0)
return -1;
+ /* A new frame must not begin before the current one ends (PEP 3154). Its
+ header may lie at the tail of the current frame if it drains it. */
+ if (self->frame_end >= 0) {
+ if (self->next_read_idx < self->frame_end) {
+ PyErr_SetString(state->UnpicklingError,
+ "beginning of a new frame before end of current frame");
+ return -1;
+ }
+ _Unpickler_EndFrame(self);
+ }
+
frame_len = calc_binsize(s, 8);
if (frame_len < 0) {
PyErr_Format(PyExc_OverflowError,
/* Rewind to start of frame */
self->next_read_idx -= frame_len;
+
+ /* Cap input_len at the frame end so reads can't cross it (PEP 3154);
+ _Unpickler_EndFrame() restores it when the frame is fully read. */
+ self->frame_end = self->next_read_idx + frame_len;
+ self->saved_input_len = self->input_len;
+ self->input_len = self->frame_end;
return 0;
}