Skip to content

Commit 7e9e1e4

Browse files
committed
Plain SQL restore runs with '\restrict' option to prevent harmful psql meta-commands. #9368
1 parent c6406db commit 7e9e1e4

File tree

2 files changed

+8
-36
lines changed

2 files changed

+8
-36
lines changed

docs/en_US/restore_dialog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ restore process:
2929
copy of the backed-up object.
3030
* Select *Plain* to restore a plain SQL backup. When selecting this option
3131
all the other options will not be applicable.
32+
**Note:** The plain SQL restore process is executed in the backend using
33+
the psql command and the \restrict option. The purpose of \restrict is to
34+
enhance security by preventing a malicious superuser from an untrusted
35+
database from embedding harmful psql meta-commands within a plain-text dump
36+
file that could be executed on a pgAdmin server during restoration.
3237
* Select *Directory* to restore from a compressed directory-format archive.
3338

3439
* Enter the complete path to the backup file in the *Filename* field.

web/pgadmin/tools/restore/__init__.py

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import json
1313
import re
14+
import secrets
1415

1516
from flask import render_template, request, current_app, Response
1617
from flask_babel import gettext as _
@@ -350,6 +351,7 @@ def get_sql_util_args(data, manager, server, filepath):
350351
:param filepath: File.
351352
:return: args list.
352353
"""
354+
restrict_key = secrets.token_hex(32)
353355
args = [
354356
'--host',
355357
manager.local_bind_host if manager.use_ssh_tunnel else server.host,
@@ -358,6 +360,7 @@ def get_sql_util_args(data, manager, server, filepath):
358360
else str(server.port),
359361
'--username', server.username, '--dbname',
360362
data['database'],
363+
'-c', f'\\restrict {restrict_key}',
361364
'--file', fs_short_path(filepath)
362365
]
363366

@@ -375,43 +378,7 @@ def use_restore_utility(data, manager, server, driver, conn, filepath):
375378
return None, utility, args
376379

377380

378-
def has_meta_commands(path, chunk_size=8 * 1024 * 1024):
379-
"""
380-
Quickly detect lines starting with '\' in large SQL files.
381-
Works even when lines cross chunk boundaries.
382-
"""
383-
# Look for start-of-line pattern: beginning or after newline,
384-
# optional spaces, then backslash
385-
pattern = re.compile(br'(^|\n)[ \t]*\\')
386-
387-
try:
388-
with open(path, "rb") as f:
389-
prev_tail = b""
390-
while chunk := f.read(chunk_size):
391-
data = prev_tail + chunk
392-
393-
# Search for pattern
394-
if pattern.search(data):
395-
return True
396-
397-
# Keep a small tail to preserve line boundary context
398-
prev_tail = data[-10:] # keep last few bytes
399-
except FileNotFoundError:
400-
current_app.logger.error("File not found.")
401-
except PermissionError:
402-
current_app.logger.error("Insufficient permissions to access.")
403-
404-
return False
405-
406-
407381
def use_sql_utility(data, manager, server, filepath):
408-
# Check the meta commands in file.
409-
if has_meta_commands(filepath):
410-
return _("Restore blocked: the selected PLAIN SQL file contains psql "
411-
"meta-commands (for example \\! or \\i). For safety, "
412-
"pgAdmin does not execute meta-commands from PLAIN restores. "
413-
"Please remove meta-commands."), None, None
414-
415382
utility = manager.utility('sql')
416383
ret_val = does_utility_exist(utility)
417384
if ret_val:

0 commit comments

Comments
 (0)