From e51761423099a2c6eec256c9291293cf78bb18ed Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Mon, 5 Jan 2026 22:12:49 +0100 Subject: [PATCH] MDEV-31632 Unresolvable outer reference causes null pointer exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SELECT 1 union select 2 UNION SELECT 1 from a JOIN a b ON (SELECT 1 FROM dual WHERE AAA) Crashes during fix_outer_field while resolving field item AAA In our resolver, once we have determined that a field item isn't local to our select, we call Item::fix_outer_field(), which iterates outwards towards the top level select, looking for where our Item_field might be resolvable. In our example here, the item isn't resolvable so we expose fragility in the loop, which i will detail here. After we initialize the variable 'outer_context' ( to a context containing /* select#3 */ select 1 AS `1` from (a join a b on ((subquery#4))) ) we enter a loop │ 5927 for (; │ 5928 outer_context; │ 5929 outer_context= outer_context->outer_context) │ 5930 { │ 5931 select= outer_context->select_lex; │ 5932 Item_subselect *prev_subselect_item= │ 5933 last_checked_context->select_lex->master_unit()->item; │ 5934 last_checked_context= outer_context; here 'last_checked_context' is the context inner to the current 'outer_context', and we initialize prev_subselect_item to the Item enclosing the unit containing this inner select. So for the first iteration of the loop, select: select #3 last_checked_context: from select #4 to select #3. prev_subselect_item: item enclosing select #4 (where field item AAA is defined) The rest of the loop calls find_field_in_tables() / resolve_ref_in_select_and_group() in and attempt to resolve this item with this 'outer_context'. After the item fails resolution, we move to an outer context select: select #4294967295 (fake_select_lex) last_checked_context: from select #3 to the fake select lex containing the union (i.e. outermost) prev_subselect_item: null, there is no Item that contains this, it is the outermost select. We still need to execute the rest of the loop to determine whether AAA is resolvable here, but executing │ 5937 place= prev_subselect_item->parsing_place; We are now following a null pointer. We introduce a test for this null pointer, indicating that we are now evaluating the outermost select and we are not try and access the enclosing subselect item. --- mysql-test/main/subselect4.result | 6 ++++++ mysql-test/main/subselect4.test | 9 +++++++++ sql/item.cc | 31 ++++++++++++++++++++++++------- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/mysql-test/main/subselect4.result b/mysql-test/main/subselect4.result index c0c67707231c4..2e6d57c68fd14 100644 --- a/mysql-test/main/subselect4.result +++ b/mysql-test/main/subselect4.result @@ -3372,5 +3372,11 @@ drop table t1,t2,t3; CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; (SELECT 0 GROUP BY c HAVING (SELECT c)) +# +# MDEV-31632 Unresolvable outer reference causes null pointer exception +# +SELECT 1 union select 2 UNION SELECT 1 from t JOIN t b ON +(SELECT 1 FROM dual WHERE AAA); +ERROR 42S22: Unknown column 'AAA' in 'WHERE' DROP TABLE t; # End of 10.11 tests diff --git a/mysql-test/main/subselect4.test b/mysql-test/main/subselect4.test index 083da1647b27c..f3d68aae1739f 100644 --- a/mysql-test/main/subselect4.test +++ b/mysql-test/main/subselect4.test @@ -2693,6 +2693,15 @@ drop table t1,t2,t3; CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; + +--echo # +--echo # MDEV-31632 Unresolvable outer reference causes null pointer exception +--echo # + +--error ER_BAD_FIELD_ERROR +SELECT 1 union select 2 UNION SELECT 1 from t JOIN t b ON + (SELECT 1 FROM dual WHERE AAA); + DROP TABLE t; --echo # End of 10.11 tests diff --git a/sql/item.cc b/sql/item.cc index da3219d7545e4..b584df6912133 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5922,10 +5922,19 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) select= outer_context->select_lex; Item_subselect *prev_subselect_item= last_checked_context->select_lex->master_unit()->item; + /* + We have merged to the top select and this field is no longer outer, + or we have reached the outermost select without resolution + */ + bool at_top= !prev_subselect_item; last_checked_context= outer_context; upward_lookup= TRUE; - place= prev_subselect_item->parsing_place; + if (!at_top) + place= prev_subselect_item->parsing_place; + else + place= NO_MATTER; + /* If outer_field is set, field was already found by first call to find_field_in_tables(). Only need to find appropriate context. @@ -5980,8 +5989,11 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) } if (*from_field != view_ref_found) { - prev_subselect_item->used_tables_cache|= (*from_field)->table->map; - prev_subselect_item->const_item_cache= 0; + if (!at_top) + { + prev_subselect_item->used_tables_cache|= (*from_field)->table->map; + prev_subselect_item->const_item_cache= 0; + } set_field(*from_field); if (!last_checked_context->select_lex->having_fix_field && select->group_list.elements && @@ -6030,7 +6042,8 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) else { Item::Type ref_type= (*reference)->type(); - prev_subselect_item->used_tables_and_const_cache_join(*reference); + if (!at_top) + prev_subselect_item->used_tables_and_const_cache_join(*reference); mark_as_dependent(thd, last_checked_context->select_lex, context->select_lex, this, ((ref_type == REF_ITEM || ref_type == FIELD_ITEM) ? @@ -6064,7 +6077,8 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (ref != not_found_item) { DBUG_ASSERT(*ref && (*ref)->fixed()); - prev_subselect_item->used_tables_and_const_cache_join(*ref); + if (!at_top) + prev_subselect_item->used_tables_and_const_cache_join(*ref); break; } } @@ -6074,8 +6088,11 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) outer select (or we just trying to find wrong identifier, in this case it does not matter which used tables bits we set) */ - prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; - prev_subselect_item->const_item_cache= 0; + if (!at_top) + { + prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; + prev_subselect_item->const_item_cache= 0; + } } DBUG_ASSERT(ref != 0);