Skip to content

Commit 9675498

Browse files
authored
Add startup permissions checks (#1596)
Some files in the extension dir need to be executable for proper operation, and some packaging methods (GitHub Actions Artifacts) may create extension bundles with the executable bits removed. So, on startup we check those files, and make them executable if necessary. (Only if we're installed as a user extension.)
1 parent ef1d402 commit 9675498

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/extension.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,14 @@ var serviceIndicator = null;
364364
var lockscreenInput = null;
365365

366366
function init() {
367+
// If installed as a user extension, this checks the permissions
368+
// on certain critical files in the extension directory
369+
// to ensure that they have the executable bit set,
370+
// and makes them executable if not. Some packaging methods
371+
// (particularly GitHub Actions artifacts) automatically remove
372+
// executable bits from all contents, presumably for security.
373+
Utils.ensurePermissions();
374+
367375
// If installed as a user extension, this will install the Desktop entry,
368376
// DBus and systemd service files necessary for DBus activation and
369377
// GNotifications. Since there's no uninit()/uninstall() hook for extensions

src/shell/utils.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,55 @@ function _installResource(dirname, basename, relativePath) {
128128
}
129129
}
130130

131+
/**
132+
* Use Gio.File to ensure a file's executable bits are set.
133+
*
134+
* @param {string} filepath - An absolute path to a file
135+
* @returns {boolean} - True if the file already was, or is now, executable
136+
*/
137+
function _setExecutable(filepath) {
138+
try {
139+
const file = Gio.File.new_for_path(filepath);
140+
const finfo = file.query_info(
141+
`${Gio.FILE_ATTRIBUTE_STANDARD_TYPE},${Gio.FILE_ATTRIBUTE_UNIX_MODE}`,
142+
Gio.FileQueryInfoFlags.NO_FOLLOW_SYMLINKS,
143+
null);
144+
145+
if (!finfo.has_attribute(Gio.FILE_ATTRIBUTE_UNIX_MODE))
146+
return false;
147+
148+
const mode = finfo.get_attribute_uint32(
149+
Gio.FILE_ATTRIBUTE_UNIX_MODE);
150+
const new_mode = (mode | 0o111);
151+
if (mode === new_mode)
152+
return true;
153+
154+
return file.set_attribute_uint32(
155+
Gio.FILE_ATTRIBUTE_UNIX_MODE,
156+
new_mode,
157+
Gio.FileQueryInfoFlags.NO_FOLLOW_SYMLINKS,
158+
null);
159+
} catch (e) {
160+
logError(e, 'GSConnect');
161+
return false;
162+
}
163+
}
164+
165+
/**
166+
* Ensure critical files in the extension directory have the
167+
* correct permissions.
168+
*/
169+
function ensurePermissions() {
170+
if (Config.IS_USER) {
171+
const executableFiles = [
172+
'gsconnect-preferences',
173+
'service/daemon.js',
174+
'service/nativeMessagingHost.js',
175+
];
176+
for (const file of executableFiles)
177+
_setExecutable(GLib.build_filenamev([Extension.path, file]));
178+
}
179+
}
131180

132181
/**
133182
* Install the files necessary for the GSConnect service to run.
@@ -221,4 +270,3 @@ function installService() {
221270
GLib.unlink(GLib.build_filenamev([manifest[0], manifestFile]));
222271
}
223272
}
224-

0 commit comments

Comments
 (0)