Skip to content

Commit 4e01a36

Browse files
nikiciluuu1994
authored andcommitted
Delay notice emission until end of opcode
This is a prototype for fixing a long-standing source of interrupt vulnerabilities: A notice is emitted during execution of an opcode, resulting in an error handling being run. The error handler modifies some data structure the opcode is working on, resulting in UAF or other memory corruption. The idea here is to instead collect notices and only process them after the opcode. This is implemented similarly to exception handling, by switching to a ZEND_HANDLE_DELAYED_ERROR opcode, which will then switch back to the normal opcode stream. Unfortunately, what this prototype implements is not sufficient. Opcodes that acquire direct (INDIRECT) references to zvals require that no interrupts occur between the producing and the consuming opcode. Chains of W/RW opcodes should be executed without interrupt. Currently, the notice is only delayed until after the first opcode, which still results in an illegal interrupt (bug78598.phpt shows a UAF with this change). I'm not sure how to best handle that issue.
1 parent 02d3eec commit 4e01a36

12 files changed

+675
-562
lines changed

Zend/zend.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,18 @@ static void zend_init_exception_op(void) /* {{{ */
667667
}
668668
/* }}} */
669669

670+
static void zend_init_delayed_error_op(void) /* {{{ */
671+
{
672+
memset(EG(delayed_error_op), 0, sizeof(EG(delayed_error_op)));
673+
EG(delayed_error_op)[0].opcode = ZEND_HANDLE_DELAYED_ERROR;
674+
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op));
675+
EG(delayed_error_op)[1].opcode = ZEND_HANDLE_DELAYED_ERROR;
676+
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op)+1);
677+
EG(delayed_error_op)[2].opcode = ZEND_HANDLE_DELAYED_ERROR;
678+
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op)+2);
679+
}
680+
/* }}} */
681+
670682
static void zend_init_call_trampoline_op(void) /* {{{ */
671683
{
672684
memset(&EG(call_trampoline_op), 0, sizeof(EG(call_trampoline_op)));
@@ -781,6 +793,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
781793
zend_copy_constants(executor_globals->zend_constants, GLOBAL_CONSTANTS_TABLE);
782794
zend_init_rsrc_plist();
783795
zend_init_exception_op();
796+
zend_init_delayed_error_op();
784797
zend_init_call_trampoline_op();
785798
memset(&executor_globals->trampoline, 0, sizeof(zend_op_array));
786799
executor_globals->capture_warnings_during_sccp = 0;
@@ -1020,6 +1033,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
10201033
#ifndef ZTS
10211034
zend_init_rsrc_plist();
10221035
zend_init_exception_op();
1036+
zend_init_delayed_error_op();
10231037
zend_init_call_trampoline_op();
10241038
#endif
10251039

@@ -1704,6 +1718,26 @@ ZEND_API void zend_free_recorded_errors(void)
17041718
EG(num_errors) = 0;
17051719
}
17061720

1721+
ZEND_API ZEND_COLD void zend_error_delayed(int type, const char *format, ...) {
1722+
ZEND_ASSERT(!(type & E_FATAL_ERRORS) && "Cannot delay fatal error");
1723+
zend_error_info *info = emalloc(sizeof(zend_error_info));
1724+
info->type = type;
1725+
get_filename_lineno(type, &info->filename, &info->lineno);
1726+
zend_string_addref(info->filename);
1727+
1728+
va_list args;
1729+
va_start(args, format);
1730+
info->message = zend_vstrpprintf(0, format, args);
1731+
va_end(args);
1732+
1733+
zend_hash_next_index_insert_ptr(&EG(delayed_errors), info);
1734+
1735+
if (EG(current_execute_data)->opline != EG(delayed_error_op)) {
1736+
EG(opline_before_exception) = EG(current_execute_data)->opline;
1737+
EG(current_execute_data)->opline = EG(delayed_error_op);
1738+
}
1739+
}
1740+
17071741
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */
17081742
{
17091743
va_list va;

Zend/zend.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ extern ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename);
343343
extern ZEND_API zend_result (*zend_post_startup_cb)(void);
344344
extern ZEND_API void (*zend_post_shutdown_cb)(void);
345345

346+
/* Callback for loading of not preloaded part of the script */
347+
extern ZEND_API zend_result (*zend_preload_autoload)(zend_string *filename);
348+
346349
ZEND_API ZEND_COLD void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
347350
ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
348351
/* For custom format specifiers like H */
@@ -352,6 +355,7 @@ ZEND_API ZEND_COLD void zend_error_at(int type, zend_string *filename, uint32_t
352355
ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_at_noreturn(int type, zend_string *filename, uint32_t lineno, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 4, 5);
353356
ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message);
354357
ZEND_API ZEND_COLD void zend_error_zstr_at(int type, zend_string *filename, uint32_t lineno, zend_string *message);
358+
ZEND_API ZEND_COLD void zend_error_delayed(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
355359

356360
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
357361
ZEND_API ZEND_COLD void zend_type_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2);

Zend/zend_execute.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,11 @@ ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_offset_write(HashTable *ht
22262226
return zend_hash_index_add_new(ht, lval, &EG(uninitialized_zval));
22272227
}
22282228

2229+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_undefined_offset_delayed(zend_long lval)
2230+
{
2231+
zend_error_delayed(E_WARNING, "Undefined array key " ZEND_LONG_FMT, lval);
2232+
}
2233+
22292234
ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_index_write(HashTable *ht, zend_string *offset)
22302235
{
22312236
zval *retval;
@@ -2495,7 +2500,8 @@ static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht
24952500
retval = &EG(uninitialized_zval);
24962501
break;
24972502
case BP_VAR_RW:
2498-
retval = zend_undefined_offset_write(ht, hval);
2503+
zend_undefined_offset_delayed(hval);
2504+
retval = zend_hash_index_add_new(ht, hval, &EG(uninitialized_zval));
24992505
break;
25002506
}
25012507
} else {

Zend/zend_execute.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_invalid_class_constant_type_error(uin
8989
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_property_error(const zend_property_info *info);
9090

9191
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void);
92+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_undefined_offset_delayed(zend_long lval);
9293

9394
ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg);
9495
ZEND_API ZEND_COLD void zend_verify_arg_error(

Zend/zend_execute_API.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ void init_executor(void) /* {{{ */
201201

202202
zend_max_execution_timer_init();
203203
zend_fiber_init();
204+
205+
zend_hash_init(&EG(delayed_errors), 0, NULL, NULL, 0);
206+
204207
zend_weakrefs_init();
205208

206209
EG(active) = 1;

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ struct _zend_executor_globals {
246246
zend_object *exception, *prev_exception;
247247
const zend_op *opline_before_exception;
248248
zend_op exception_op[3];
249+
zend_op delayed_error_op[3];
249250

250251
struct _zend_module_entry *current_module;
251252

@@ -303,6 +304,7 @@ struct _zend_executor_globals {
303304
pid_t pid;
304305
struct sigaction oldact;
305306
#endif
307+
HashTable delayed_errors;
306308

307309
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
308310
};

Zend/zend_vm_def.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8090,6 +8090,31 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
80908090
ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, current_try_catch_offset, op_num, throw_op_num);
80918091
}
80928092

8093+
ZEND_VM_HANDLER(204, ZEND_HANDLE_DELAYED_ERROR, ANY, ANY)
8094+
{
8095+
const zend_op *next_op = EG(opline_before_exception) + 1;
8096+
if (next_op->opcode == ZEND_OP_DATA) {
8097+
next_op++;
8098+
}
8099+
8100+
/* Clear EG(delayed_errors), as more errors may be delayed while we are handling these. */
8101+
HashTable ht;
8102+
memcpy(&ht, &EG(delayed_errors), sizeof(HashTable));
8103+
zend_hash_init(&EG(delayed_errors), 0, NULL, NULL, 0);
8104+
8105+
zend_error_info *info;
8106+
ZEND_HASH_FOREACH_PTR(&ht, info) {
8107+
zend_error_zstr_at(info->type, info->filename, info->lineno, info->message);
8108+
zend_string_release(info->filename);
8109+
zend_string_release(info->message);
8110+
efree(info);
8111+
} ZEND_HASH_FOREACH_END();
8112+
zend_hash_destroy(&ht);
8113+
8114+
ZEND_VM_SET_NEXT_OPCODE(next_op);
8115+
ZEND_VM_CONTINUE();
8116+
}
8117+
80938118
ZEND_VM_HANDLER(150, ZEND_USER_OPCODE, ANY, ANY)
80948119
{
80958120
USE_OPLINE

0 commit comments

Comments
 (0)