Skip to content

Commit bd28225

Browse files
committed
gh-153569: strengthen tokenizer validation
1 parent a981d04 commit bd28225

2 files changed

Lines changed: 219 additions & 84 deletions

File tree

Modules/_xxtestfuzz/fuzzer.c

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
#include <stdlib.h>
1919
#include <inttypes.h>
2020

21+
#if defined(_Py_FUZZ_ONE) && defined(_Py_FUZZ_fuzz_tokenizer) && \
22+
defined(HAVE_TMPFILE) && !defined(Py_ENABLE_SHARED)
23+
# define FUZZ_TOKENIZER_FILE 1
24+
# include "../../Include/internal/pycore_token.h"
25+
# include "../../Parser/tokenizer/tokenizer.h"
26+
#endif
27+
2128
/* Fuzz PyFloat_FromString as a proxy for float(str). */
2229
static int fuzz_builtin_float(const char* data, size_t size) {
2330
PyObject* s = PyBytes_FromStringAndSize(data, size);
@@ -623,7 +630,6 @@ clear_expected_tokenizer_error(void)
623630
if (PyErr_ExceptionMatches(PyExc_SyntaxError) ||
624631
PyErr_ExceptionMatches(PyExc_UnicodeError) ||
625632
PyErr_ExceptionMatches(PyExc_LookupError) ||
626-
PyErr_ExceptionMatches(PyExc_MemoryError) ||
627633
PyErr_ExceptionMatches(PyExc_Warning)) {
628634
PyErr_Clear();
629635
return;
@@ -632,6 +638,82 @@ clear_expected_tokenizer_error(void)
632638
abort();
633639
}
634640

641+
#ifdef FUZZ_TOKENIZER_FILE
642+
static int
643+
fuzz_tokenizer_file(const char *data, size_t size, unsigned char options)
644+
{
645+
FILE *fp = tmpfile();
646+
if (fp == NULL) {
647+
return 0;
648+
}
649+
if ((size != 0 && fwrite(data, 1, size, fp) != size) ||
650+
fseek(fp, 0, SEEK_SET) != 0) {
651+
fclose(fp);
652+
return 0;
653+
}
654+
655+
PyObject *filename = PyUnicode_FromString("<fuzz input>");
656+
if (filename == NULL) {
657+
fclose(fp);
658+
clear_expected_tokenizer_error();
659+
return 1;
660+
}
661+
_PyTok_Config config = {
662+
.kind = _PYTOK_SOURCE_FILE,
663+
.source.file = {fp, NULL, NULL, NULL},
664+
.extra_tokens = options & 0x02,
665+
.filename = filename,
666+
};
667+
PyTokenizer *tok = _PyTok_New(&config);
668+
if (tok == NULL) {
669+
_PyTok_RaiseInitError(filename);
670+
Py_DECREF(filename);
671+
fclose(fp);
672+
clear_expected_tokenizer_error();
673+
return 1;
674+
}
675+
Py_DECREF(filename);
676+
677+
int terminated = 0;
678+
size_t limit = size * 4 + 32;
679+
for (size_t i = 0; i < limit; i++) {
680+
_PyTok_Token token;
681+
_PyTok_TokenInit(&token);
682+
if (_PyTok_Get(tok, &token) == _PYTOK_ERROR) {
683+
_PyTok_TokenClear(&token);
684+
_PyTok_RaiseError(tok);
685+
clear_expected_tokenizer_error();
686+
terminated = 1;
687+
break;
688+
}
689+
int type = token.type;
690+
Py_ssize_t token_len;
691+
_PyTok_Loc start;
692+
_PyTok_Loc end;
693+
if (_PyTok_TokenView(tok, &token, &token_len) == NULL ||
694+
token_len < 0 ||
695+
_PyTok_TokenTextLocations(tok, &token, &start, &end) < 0 ||
696+
start.lineno < 1 || start.byte_col < -1 ||
697+
end.lineno < start.lineno || end.byte_col < -1 ||
698+
(end.lineno == start.lineno &&
699+
end.byte_col < start.byte_col)) {
700+
abort();
701+
}
702+
_PyTok_TokenClear(&token);
703+
if (type == ENDMARKER) {
704+
terminated = 1;
705+
break;
706+
}
707+
}
708+
_PyTok_Free(tok);
709+
fclose(fp);
710+
if (!terminated) {
711+
abort();
712+
}
713+
return 1;
714+
}
715+
#endif
716+
635717
static int
636718
fuzz_tokenizer(const char *data, size_t size)
637719
{
@@ -643,6 +725,12 @@ fuzz_tokenizer(const char *data, size_t size)
643725
data++;
644726
size--;
645727

728+
#ifdef FUZZ_TOKENIZER_FILE
729+
if ((options & 0x04) && fuzz_tokenizer_file(data, size, options)) {
730+
return 0;
731+
}
732+
#endif
733+
646734
int bytes_mode = options & 0x01;
647735
PyObject *source;
648736
if (bytes_mode) {

0 commit comments

Comments
 (0)