Sailfish OS application sandboxing and permissions

April 21, 2023 · View on GitHub

Sailfish OS application sandboxing and permissions system is build on top of Firejail. The Firejail documentation can be found from here.

The Sailfish OS application permissions are applicable only to sandboxed applications. Since Sailfish OS 4.4.0 all applications that do not specifically opt-out or define permissions are sandboxed automatically using a default profile.

Application launch happens via standard desktop file that has been augmented so that it contains necessary metadata (such as a list of requested permissions) and Sailjail launcher is used to execute the application binary.

Permissions that an application can request are defined in this package, in terms of files containing standard Firejail directives and Sailfish OS specific metadata.

Enable sandboxing for an application

There are few changes needed for an application in order to run it in a sandbox.

Desktop file changes

Let's go through needed changes via an example.

[Desktop Entry]
Type=Application
Name=MyApplication
Icon=my-app-icon
Exec=harbour-myapp

[X-Sailjail]
Permissions=Internet;Pictures
OrganizationName=org.foobar
ApplicationName=MyApp

The Exec line should match the name of the desktop file in /usr/share/applications. If the name does not match, use /usr/bin/sailjail to start the application and pass the file name of the desktop file with -p option. If you are developing an app which is intended for Harbour, you should not use path on the Exec line. In other cases, refer to the application binary with the full path.

To declare permissions and data directories you need to add X-Sailjail section to the desktop file. This is called an application profile. Under the X-Sailjail section add

KeywordDescription
PermissionsSemi-colon separated list of requested permissions
OrganizationNameApplication development organization as a reverse domain name
ApplicationNameApplication name

Permissions are listed later in the document. They grant access to certain data paths, D-Bus interfaces, socket types and application binaries. Currently applications must define all needed permissions in desktop file and all of them are granted at launch.

OrganizationName and ApplicationName is used for granting for the application write access to

  1. $HOME/.local/share/<OrganizationName>/<ApplicationName>
  2. $HOME/.cache/<OrganizationName>/<ApplicationName>
  3. $HOME/.config/<OrganizationName>/<ApplicationName>

Access above directories from the application through QStandardPaths

  1. Application data location - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
  2. Application cache location - QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
  3. Application config location - QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)

Only these directories can be used for storing application specific data that needs to persist over application restarts.

Use correct application data directories

Applications that are not using the Sailfish App library (libsailfishapp) for their initialization must ensure that correct organization and application name is set.

...
QGuiApplication *app = ...;
app->setOrganizationName(QStringLiteral("org.foobar"));
app->setApplicationName(QStringLiteral("MyApp"));
...

When Sailfish App library is used, these values are set automatically to the values set in application's Desktop Entry file.

Sandboxed applications cannot use QSettings constructed with the default arguments - it would use an inaccessible path outside QStandardPaths::AppDataLocation. A suitable settings file path needs to be determined explicitly.

const QString settingsPath =
    QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)
    + "/" + QCoreApplication::applicationName() + ".conf";
QSettings settings(settingsPath, QSettings::NativeFormat);

Unless you are creating a new application, it is also necessary to take care about migrating application data and configuration from the old location outside the sandbox.

An example of how QSettings-based configuration may be migrated:

if (!settings.contains("migrated")) {
    const QString oldSettingsPath =
        QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)
        + "/" + QCoreApplication::applicationName()
        + "/" + QCoreApplication::applicationName() + ".conf";
    QSettings oldSettings(oldSettingsPath, QSettings::NativeFormat);

    for (const QString &key : oldSettings.childKeys())
        settings.setValue(key, oldSettings.value(key));

    settings.setValue("migrated", "true");
}

Similarly with QML LocalStorage:

QDir dbDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
        + "/QML/OfflineStorage/Databases/");
if (!dbDir.exists()) {
    QDir oldDbDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
            + "/" + QCoreApplication::applicationName()
            + "/" + QCoreApplication::applicationName()
            + "/QML/OfflineStorage/Databases/");

    dbDir.mkpath(".");

    const QStringList dbFiles = oldDbDir.entryList({"*.sqlite", "*.ini"}, QDir::Files);
    for (const QString &dbFile : dbFiles) {
        if (!QFile::copy(oldDbDir.filePath(dbFile), dbDir.filePath(dbFile)))
            ...
    }
}

Files shared with other applications

Directories under user home such as Documents, Downloads, Music, Pictures and Videos contain files that are available for applications with respective permissions. Those directories must be used when the data needs to be accessible by other applications.

If application doesn't have a permission for a directory, all data in that directory will be hidden and the application sees only an empty read-only directory in that path. This allows to regular file access checks to function in expected way.

Sandboxing of applications without application profile

If application does not define application profile, i.e. X-Sailjail section in its desktop file, a default profile may be applied. This is defined by configuration (see config/50-default-profile.conf) and applies a relaxed set of permissions which should be compatible with most existing and well-behaving applications. It specifically does not grant access to any sensitive data normally protected by privileged group.

Some assumptions about the application are made:

  • The application has only one binary as specified by Exec key in desktop file
  • The application installs its own files in /usr/share/<app binary name>
  • The application stores its own private data in ~/.local/share/<app binary name>
  • The application stores its config data in ~/.config/<app binary name>
  • The application stores its cached data in ~/.cache/<app binary name>
  • The application stores common data in user directories as specified by UserDirs or on memory card
  • The application doesn't need access to other application's data outside those common directories
  • The application doesn't need access to privileged data
  • The application doesn't need access to privileged or otherwise private D-Bus APIs

Permissions

Permissions that applications may use (names are subject to change):

PermissionDescription
AccountsUsing accounts, including editing them. Syncing accounts.
AmbienceSet and edit ambiences.
AppLaunchLaunching and stopping systemd services. This is usually needed for background tasks.
ApplicationInstallationInstalling and uninstalling applications.
AudioPlaying and recording audio (since Pulseaudio streams cannot be separated both are enabled with this, but it is subject to change), changing audio configuration and showing audio controls on lockscreen.
BluetoothConnecting to and using Bluetooth hardware.
CalendarDisplay and editing of calendar events.
CallRecordingsAccess recorded calls.
CameraAccess to camera hardware to take photos or video.
CommunicationHistoryAccess call and message history.
ContactsDisplay and editing of contacts data. Access to contact cards.
DocumentsAccess to Documents directory.
DownloadsAccess to Downloads directory.
EmailReading and sending emails. Access to email attachments.
InternetUsing data connection and connecting to internet.
LocationUse GPS and positioning.
MediaIndexingAccess to Tracker to list files on device. If you have access to a data directory, you may want to use also this.
MessagesAccess to message data and to send SMS messages.
MicrophoneRecord audio with microphone. Use Audio permission for playback of the recorded audio (but since Pulseaudio streams cannot be separated this enables also audio playback, which is subject to change).
MusicAccess to Music directory, playlists and coverart cache.
NFCConnecting to and using NFC hardware.
PhoneMake Phone calls, either directly or through system voice call UI.
PicturesAccess to Pictures directory and thumbnails.
PublicDir Access to Public directory.
RemovableMediaUse memory cards and USB sticks.
SecretsAccess to Sailfish Secrets. Since 4.5.0
SynchronizationAccess to synchronization framework.
UserDirsAccess to Documents, Downloads, Music, Pictures, Public and Video directories.
VideosAccess to Videos directory and thumbnails.
WebViewIf you use Gecko based WebView you need this.

Internal permissions that applications generally should not use directly:

PermissionDescription
BaseBase set of permissions that every application is granted implicitly.
CaptivePortal
CompatibilityAllows for accessing certain binaries to provide some degree of compatibility for extant apps. In general, only library classes and functions are allowed to be used by new applications. The access to these binaries may disappear in any Sailfish OS release, especially when Jolla provides APIs with similar functionality.
Connman
GnuPG
FingerprintSensor
Notifications
PinQuery
PrivilegedAccess to privileged data.
Sensors
SharingAllows an application to access the Share API, for sending data to other applications. The Share API is fully mediated and under the user's explicit control, and is therefore considered low risk. Consequently this permission is contained as part of the Base permission and hence implicitly granted to all applications.
Thumbnails
UDisksPermissions to call UDisks functions, includes UDisksListen.
UDisksListenPermissions to listen signals and property changes on UDisks2 interfaces.

Permission metadata format

The metadata is used to generate translations build time and to display useful information on UI about the permissions.

Permission files have metadata in their comments. Each metadata line begins with comment character '#' and key that begins with x-sailjail- followed by '=' character and the value. Below you can find the keys that permissions should define.

KeywordDescription
x-sailjail-translation-catalogThis key defines Qt translation catalog name which translations for the metadata should be retrieved from
x-sailjail-translation-key-descriptionThis key defines translation key for short description
x-sailjail-descriptionThis key defines engineering english text for short description
x-sailjail-translation-key-long-descriptionThis key defines translation key for long description
x-sailjail-long-descriptionThis key defines engineering english text for long description.