Skip to content

Commit e306cd8

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

File tree

5 files changed

+8
-67
lines changed

5 files changed

+8
-67
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/backup/static/js/backup.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,6 @@ define([
157157
let extraData = this.setExtraParameters(typeOfDialog);
158158
this.showBackupDialog(gettext('Backup Server'), schema, treeItem, typeOfDialog, extraData);
159159
},
160-
saveCallBack: function(data) {
161-
if(data.errormsg) {
162-
pgAdmin.Browser.notifier.alert(
163-
gettext('Error'),
164-
gettext(data.errormsg)
165-
);
166-
} else {
167-
168-
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
169-
}
170-
},
171160
url_for_utility_exists(id, params){
172161
return url_for('backup.utility_exists', {
173162
'sid': id,

web/pgadmin/tools/maintenance/static/js/maintenance.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,6 @@ define([
9292
}
9393
);
9494
},
95-
saveCallBack: function(data) {
96-
if(data.errormsg) {
97-
pgAdmin.Browser.notifier.alert(
98-
gettext('Error'),
99-
gettext(data.errormsg)
100-
);
101-
} else {
102-
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
103-
}
104-
},
10595
setExtraParameters(treeInfo) {
10696
let extraData = {};
10797
extraData['database'] = treeInfo.database._label;

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:

web/pgadmin/tools/restore/static/js/restore.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,6 @@ define('tools.restore', [
9191
pgBrowser
9292
);
9393
},
94-
saveCallBack: function(data) {
95-
if(data.errormsg) {
96-
pgAdmin.Browser.notifier.alert(
97-
gettext('Error'),
98-
gettext(data.errormsg)
99-
);
100-
} else {
101-
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
102-
}
103-
},
10494
setExtraParameters: function(treeInfo, nodeData) {
10595
let extraData = {};
10696
extraData['database'] = treeInfo.database._label;

0 commit comments

Comments
 (0)