diff --git a/files/qa-databases.conf b/files/qa-databases.conf index 4a74abc4..c4fd3460 100644 --- a/files/qa-databases.conf +++ b/files/qa-databases.conf @@ -385,3 +385,16 @@ db_repl_8766_alias = $(dir_sampleDb)/qa_replication/db_repl_8766.fdb # db_sync_main_alias = $(dir_sampleDb)/qa_replication/db_sync_main.fdb db_sync_repl_alias = $(dir_sampleDb)/qa_replication/db_sync_repl.fdb + +# Tests for external table path validation. +# Companion tests for FirebirdSQL/firebird#9121. + +tmp_external_file_access_denied_alias = $(dir_sampleDb)/qa/tmp_external_file_access_denied.fdb +{ + ExternalFileAccess = Restrict $(dir_sampleDb)/qa +} + +tmp_external_file_allowed_alias = $(dir_sampleDb)/qa/tmp_external_file_allowed.fdb +{ + ExternalFileAccess = Restrict $(dir_sampleDb)/qa +} diff --git a/tests/functional/gtcs/test_external_file_access_denied.py b/tests/functional/gtcs/test_external_file_access_denied.py new file mode 100644 index 00000000..c9d14a85 --- /dev/null +++ b/tests/functional/gtcs/test_external_file_access_denied.py @@ -0,0 +1,71 @@ +#coding:utf-8 + +""" +ID: gtcs.external-file-access-denied +FBTEST: functional.gtcs.external_file_access_denied +TITLE: External file path outside configured directories is rejected at DDL time +DESCRIPTION: + When ExternalFileAccess is restricted, creating an external table whose + file path is outside the allowed directory must be rejected during + CREATE TABLE. + + Without the fix, CREATE TABLE succeeds and the invalid path is stored + in RDB$EXTERNAL_FILE. The access check is performed only when the + external file is opened. + + With the fix, the path is validated while relation metadata is loaded, + so CREATE TABLE fails immediately. + +NOTES: + [08.08.2026] sunliqiang + The test database uses a dedicated databases.conf alias with + ExternalFileAccess restricted to its database directory. +""" + +from pathlib import Path + +import pytest +from firebird.qa import * + + +REQUIRED_ALIAS = 'tmp_external_file_access_denied_alias' + +db = db_factory(filename='#' + REQUIRED_ALIAS) + +act = isql_act('db') + + +@pytest.mark.version('>=4.0') +def test_1(act: Action): + + # Obtain the physical database location. The database is created inside + # the directory allowed by ExternalFileAccess. + with act.db.connect() as con: + cur = con.cursor() + cur.execute('select mon$database_name from mon$database') + db_path = Path(cur.fetchone()[0]) + + allowed_dir = db_path.parent + + # Use a file in the parent directory, which is deliberately outside + # ExternalFileAccess = Restrict . + denied_file = allowed_dir.parent / 'ext_access_denied_test.dat' + + external_file = str(denied_file).replace("'", "''") + + sql = f""" + create table ext_escape external file '{external_file}' + ( + line varchar(200) + ); + exit; + """ + + act.expected_stderr = f""" +Statement failed, SQLSTATE = 28000 +Use of external file at location {denied_file} is not allowed by server configuration +""" + + act.isql(switches=['-q'], input=sql) + + assert act.clean_stderr == act.clean_expected_stderr diff --git a/tests/functional/gtcs/test_external_file_allowed_dir.py b/tests/functional/gtcs/test_external_file_allowed_dir.py new file mode 100644 index 00000000..a57275a8 --- /dev/null +++ b/tests/functional/gtcs/test_external_file_allowed_dir.py @@ -0,0 +1,84 @@ +#coding:utf-8 + +""" +ID: gtcs.external-file-allowed-dir +FBTEST: functional.gtcs.external_file_allowed_dir +TITLE: External table in the allowed directory still works +DESCRIPTION: + Regression test for external file path validation at metadata load time. + + When ExternalFileAccess is restricted to the directory containing the + test database, an external table whose file is located inside that + directory must still work normally. + +NOTES: + [08.08.2026] sunliqiang + The test database uses a dedicated databases.conf alias with + ExternalFileAccess restricted to its database directory. +""" + +from pathlib import Path + +import pytest +from firebird.qa import * + + +REQUIRED_ALIAS = 'tmp_external_file_allowed_alias' + +db = db_factory(filename='#' + REQUIRED_ALIAS) + +act = isql_act('db') + + +expected_stdout = """ +ID 42 +""" + + +@pytest.mark.version('>=4.0') +def test_1(act: Action): + + # The database itself is located in the directory configured in + # ExternalFileAccess, so a sibling external file is guaranteed to + # be inside the allowed directory. + with act.db.connect() as con: + cur = con.cursor() + cur.execute('select mon$database_name from mon$database') + db_path = Path(cur.fetchone()[0]) + + ext_file = db_path.parent / 'ext_access_allowed_test.dat' + + # Remove leftovers from an interrupted/failed previous test run. + ext_file.unlink(missing_ok=True) + + external_file = str(ext_file).replace("'", "''") + + sql = f""" + create table ext_ok external file '{external_file}' + ( + id int + ); + commit; + + insert into ext_ok (id) values (42); + commit; + + set list on; + select id from ext_ok; + + drop table ext_ok; + commit; + + exit; + """ + + act.expected_stdout = expected_stdout + act.expected_stderr = "" + + try: + act.isql(switches=['-q'], input=sql) + + assert act.clean_stdout == act.clean_expected_stdout + assert act.clean_stderr == act.clean_expected_stderr + finally: + ext_file.unlink(missing_ok=True)