Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions files/qa-databases.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
71 changes: 71 additions & 0 deletions tests/functional/gtcs/test_external_file_access_denied.py
Original file line number Diff line number Diff line change
@@ -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 <allowed_dir>.
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
84 changes: 84 additions & 0 deletions tests/functional/gtcs/test_external_file_allowed_dir.py
Original file line number Diff line number Diff line change
@@ -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)