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
1,255 changes: 1,227 additions & 28 deletions apps/ll-cli/src/main.cpp

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions docs/pages/en/guide/lessons/basic-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,71 @@ StartupNotify=true
Terminal=false
```

## Managing sandbox permissions with configuration files

`ll-cli config` now understands Flatseal-style permission toggles through JSON configuration files. User-level settings live in `~/.config/linglong/config.json`, application overrides live in `~/.config/linglong/apps/<appid>/config.json`, and base overrides live under `~/.config/linglong/base/<baseid>/config.json`. Settings can also be defined system-wide inside `/var/lib/linglong/config/`.

Use the new helpers to enable or disable presets:

```
ll-cli config enable-permission --global --category filesystem host home
ll-cli config disable-permission --app org.deepin.calculator --category sockets cups
```

Internally the files contain a `permissions` object. Example:

```json
{
"permissions": {
"filesystem": { "host": true, "home": true, "host-os": true },
"sockets": { "cups": true },
"portals": { "notifications": true, "background": false }
}
}
```

The recognised categories and names are:

| Category | Names | Effect (when enabled) |
| ---------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| filesystem | `host`, `host-os`, `host-etc`, `home` | Share full host tree, OS resources, `/etc`, or the host home directory (with privates) |
| sockets | `cups`, `pcsc` | Bind `/run/cups` (printing) or `/run/pcscd` (smart cards) from the host |
| portals | `background`, `notifications`, `microphone`, `speaker`, `camera`, `location` | Exported to the container as `LINGLONG_PORTAL_<NAME>` environment variables |
| devices | `usb`, `usb-hid`, `udev` | Share `/dev/bus/usb`, `/dev/hidraw*`, and `/run/udev` plus host udev rules (`LINGLONG_UDEV_RULES_DIR`) |

If a category is omitted the default matches the previous behaviour (host root, host OS assets, and the host home directory are shared). The JSON can also be edited manually when integrating with GUI tools. When `devices.udev` is enabled the host `/etc/udev/rules.d` and `/lib/udev/rules.d` directories are mapped read-only to `/run/host-udev-rules` inside the sandbox, and the location is exposed via the `LINGLONG_UDEV_RULES_DIR` variable.

Custom udev rules can be embedded directly into the configuration:

```
ll-cli config add-udev-rule --global --name 99-my-device.rules --file ./my-device.rules
ll-cli config rm-udev-rule --global --name 99-my-device.rules
```

The rule content is stored in JSON and synchronized into `/run/host-udev-rules/custom/` when the sandbox starts, so it works together with the `devices.udev` preset.

### Restrict access to `~/.config/linglong`

To prevent rogue applications from rewriting the host configuration, containers now hide `~/.config/linglong` by default. Only application IDs in the whitelist can see or modify the host directory. Maintain the whitelist via the CLI (the option must operate on `--global`):

```
ll-cli config allow-config-home --global org.deepin.calculator
ll-cli config deny-config-home --global org.deepin.calculator
```

Entries are stored under `config_access_whitelist`. Applications that are not listed receive a private copy of `~/.config/linglong` even when `filesystem.home` is enabled.

### Restrict access to `/run/host/rootfs`

The full host filesystem is exposed under `/run/host/rootfs`, but it is now hidden unless an app is explicitly whitelisted:

```
ll-cli config allow-host-root --global org.deepin.calculator
ll-cli config deny-host-root --global org.deepin.calculator
```

The entries live in `host_root_whitelist` and follow the same matching rules as the config whitelist, allowing you to pair them with `filesystem.host` permissions when necessary.

## Linyaps Application Build Project `linglong.yaml` Specification

Like other traditional package management suites, manually creating a Linyaps application build project requires setting up a build rule file `linglong.yaml`. In the build rules, it is divided into `global fields` and `custom fields` according to usage. \* In the case, all space symbols and placeholders in the `linglong.yaml` body are valid characters. Please do not delete or change the format
Expand Down
65 changes: 65 additions & 0 deletions docs/pages/guide/lessons/basic-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,71 @@ StartupNotify=true
Terminal=false
```

## 通过配置文件管理沙箱权限

`ll-cli config` 已经支持 Flatseal 风格的权限开关。用户级别的设置位于 `~/.config/linglong/config.json`,应用以及基础环境的覆盖配置分别位于 `~/.config/linglong/apps/<appid>/config.json` 与 `~/.config/linglong/base/<baseid>/config.json`,也可以在 `/var/lib/linglong/config/` 中提供系统级默认值。

常用命令如下:

```
ll-cli config enable-permission --global --category filesystem host home
ll-cli config disable-permission --app org.deepin.calculator --category sockets cups
```

配置文件中的 `permissions` 字段示例:

```json
{
"permissions": {
"filesystem": { "host": true, "home": true, "host-os": true },
"sockets": { "cups": true },
"portals": { "notifications": true, "background": false }
}
}
```

支持的类别与名称如下:

| 类别 | 名称 | 含义(启用时) |
| ---------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| filesystem | `host`、`host-os`、`host-etc`、`home` | 共享完整宿主机目录、只读系统资源、`/etc` 或宿主机家目录(自动隔离敏感目录) |
| sockets | `cups`、`pcsc` | 映射宿主机 `/run/cups`(打印)或 `/run/pcscd`(智能卡) |
| portals | `background`、`notifications`、`microphone`、`speaker`、`camera`、`location` | 通过环境变量 `LINGLONG_PORTAL_<NAME>` 传入容器,供桌面门户读取 |
| devices | `usb`、`usb-hid`、`udev` | 共享 `/dev/bus/usb`、`/dev/hidraw*` 以及 `/run/udev`/udev 规则(导出 `LINGLONG_UDEV_RULES_DIR`) |

如果某个类别没有出现在配置文件中,会采用旧版本相同的默认值(仍然共享主机根目录、系统资产以及宿主家目录)。也可以直接手动编辑 JSON 文件,方便与图形化工具联动。启用 `devices.udev` 后,宿主的 `/etc/udev/rules.d` 与 `/lib/udev/rules.d` 会以只读方式映射到容器的 `/run/host-udev-rules`,并通过 `LINGLONG_UDEV_RULES_DIR` 环境变量告知应用。

自定义 udev 规则可通过:

```
ll-cli config add-udev-rule --global --name 99-my-device.rules --file ./my-device.rules
ll-cli config rm-udev-rule --global --name 99-my-device.rules
```

`add-udev-rule` 会把文件内容直接写入配置,容器启动时会自动同步到 `/run/host-udev-rules/custom/` 目录,便于与 `devices.udev` 配合。

### 限制对 `~/.config/linglong` 的访问

为防止应用在容器内恶意修改宿主的 linyaps 配置,默认情况下容器会将 `~/.config/linglong` 隐藏到沙箱内部的私有目录。只有被加入白名单的应用才可以访问和修改该目录。使用下面的命令维护白名单(必须作用于 `--global` 配置):

```
ll-cli config allow-config-home --global org.deepin.calculator
ll-cli config deny-config-home --global org.deepin.calculator
```

白名单支持多个 APPID,也可以直接在 `config_access_whitelist` 数组里手动编辑。没有在白名单中的应用即使启用了 `filesystem.home` 依旧会看到一个与宿主隔离的 `~/.config/linglong`。

### 限制对宿主根目录 `/run/host/rootfs` 的访问

宿主根文件系统默认同样被重映射,只有加入白名单的应用可以看到真实的 `/run/host/rootfs`:

```
ll-cli config allow-host-root --global org.deepin.calculator
ll-cli config deny-host-root --global org.deepin.calculator
```

白名单存储在 `host_root_whitelist`,匹配语义与配置目录白名单一致,可直接协同 `filesystem.host` 等权限使用。

## 玲珑应用构建工程 `linglong.yaml` 规范

正如其他传统包管理套件一样,手动创建一个玲珑应用构建工程需要设置构建规则文件 `linglong.yaml`,在构建规则中,则根据用途划分为 `全局字段` 及 `定制化字段`。\* 案例中 `linglong.yaml` 正文内所有空格符号、占位符均为有效字符,请勿删除或变更格式
Expand Down
7 changes: 0 additions & 7 deletions libs/linglong/src/linglong/builder/linglong_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1878,13 +1878,6 @@ utils::error::Result<void> Builder::run(std::vector<std::string> modules,
.bindXDGRuntime()
.bindUserGroup()
.bindRemovableStorageMounts()
.bindHostRoot()
.bindHostStatics()
.bindHome(homeEnv)
.enablePrivateDir()
.mapPrivate(std::string{ homeEnv } + "/.ssh", true)
.mapPrivate(std::string{ homeEnv } + "/.gnupg", true)
.bindIPC()
.forwardDefaultEnv()
.addExtraMounts(applicationMounts)
.enableSelfAdjustingMount()
Expand Down
13 changes: 0 additions & 13 deletions libs/linglong/src/linglong/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,12 +601,6 @@ int Cli::run(const RunOptions &options)
break;
}

auto *homeEnv = ::getenv("HOME");
if (homeEnv == nullptr) {
qCritical() << "Couldn't get HOME env.";
return -1;
}

runContext.enableSecurityContext(runtime::getDefaultSecurityContexts());

linglong::generator::ContainerCfgBuilder cfgBuilder;
Expand All @@ -620,13 +614,6 @@ int Cli::run(const RunOptions &options)
.bindXDGRuntime()
.bindUserGroup()
.bindRemovableStorageMounts()
.bindHostRoot()
.bindHostStatics()
.bindHome(homeEnv)
.enablePrivateDir()
.mapPrivate(std::string{ homeEnv } + "/.ssh", true)
.mapPrivate(std::string{ homeEnv } + "/.gnupg", true)
.bindIPC()
.forwardDefaultEnv()
.enableSelfAdjustingMount();

Expand Down
62 changes: 35 additions & 27 deletions libs/linglong/src/linglong/package_manager/package_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <QUuid>

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <utility>

Expand Down Expand Up @@ -128,20 +129,6 @@ utils::error::Result<bool> PackageManager::isRefBusy(const package::Reference &r
{
LINGLONG_TRACE(fmt::format("check if ref[{}] is used by some apps", ref.toString()));

auto ret = lockRepo();
if (!ret) {
return LINGLONG_ERR(
QStringLiteral("failed to lock repo, underlying data will not be removed: %1")
.arg(ret.error().message().c_str()));
}

auto unlock = utils::finally::finally([this] {
auto ret = unlockRepo();
if (!ret) {
qCritical() << "failed to unlock repo:" << ret.error().message();
}
});
Comment on lines 131 to -157
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Removing the repository lock from isRefBusy shifts the responsibility of locking to the callers. However, the PackageManager::Uninstall D-Bus method, which is a caller of isRefBusy, does not appear to have been updated to acquire a lock. This could introduce a race condition when checking if a reference is busy and during the uninstallation process itself, potentially leading to data corruption. Please ensure that all operations modifying the repository are properly serialized with locks.


auto running = getAllRunningContainers();
if (!running) {
return LINGLONG_ERR(QStringLiteral("failed to get running containers: %1")
Expand Down Expand Up @@ -235,7 +222,7 @@ PackageManager::getAllRunningContainers() noexcept

[[nodiscard]] utils::error::Result<void> PackageManager::lockRepo() noexcept
{
LINGLONG_TRACE("lock whole repo")
LINGLONG_TRACE("lock whole repo");
lockFd = ::open(repoLockPath, O_RDWR | O_CREAT, 0644);
if (lockFd == -1) {
return LINGLONG_ERR(QStringLiteral("failed to create lock file %1: %2")
Expand All @@ -250,12 +237,13 @@ PackageManager::getAllRunningContainers() noexcept
QStringLiteral("failed to lock %1: %2").arg(repoLockPath).arg(::strerror(errno)));
}

lockStart = std::chrono::steady_clock::now();
return LINGLONG_OK;
}

[[nodiscard]] utils::error::Result<void> PackageManager::unlockRepo() noexcept
{
LINGLONG_TRACE("unlock whole repo")
LINGLONG_TRACE("unlock whole repo");

if (lockFd == -1) {
return LINGLONG_OK;
Expand All @@ -270,6 +258,14 @@ PackageManager::getAllRunningContainers() noexcept

::close(lockFd);
lockFd = -1;
if (lockStart != std::chrono::steady_clock::time_point{}) {
auto elapsed = std::chrono::steady_clock::now() - lockStart;
lockStart = {};
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
if (seconds > 5) {
qInfo() << "repository lock held for" << seconds << "seconds";
}
}

return LINGLONG_OK;
}
Expand Down Expand Up @@ -310,7 +306,7 @@ utils::error::Result<void> PackageManager::switchAppVersion(const package::Refer
const package::Reference &newRef,
bool removeOldRef) noexcept
{
LINGLONG_TRACE("remove old reference after install")
LINGLONG_TRACE("remove old reference after install");
LogI("switch app version from {} to {}", oldRef.toString(), newRef.toString());

auto res = applyApp(newRef);
Expand Down Expand Up @@ -340,16 +336,7 @@ utils::error::Result<void> PackageManager::switchAppVersion(const package::Refer

void PackageManager::deferredUninstall() noexcept
{
if (auto ret = lockRepo(); !ret) {
qCritical() << "failed to lock repo:" << ret.error().message();
return;
}
auto unlock = utils::finally::finally([this] {
auto ret = unlockRepo();
if (!ret) {
qCritical() << "failed to unlock repo:" << ret.error().message();
}
});
const auto lockStart = std::chrono::steady_clock::now();

// query layers which have been mark 'deleted'
auto uninstalled = this->repo.listLocalBy(linglong::repo::repoCacheQuery{ .deleted = true });
Expand Down Expand Up @@ -396,6 +383,27 @@ void PackageManager::deferredUninstall() noexcept
}

// begin to uninstall
auto tryLock = [this, &lockStart]() -> bool {
if (auto ret = lockRepo(); !ret) {
auto elapsed = std::chrono::steady_clock::now() - lockStart;
qCritical() << "failed to lock repo:" << ret.error().message()
<< "elapsed" << std::chrono::duration_cast<std::chrono::seconds>(elapsed).count()
<< "s";
return false;
}
return true;
};

if (!tryLock()) {
return;
}
auto unlock = utils::finally::finally([this]() {
auto ret = unlockRepo();
if (!ret) {
qCritical() << "failed to unlock repo:" << ret.error().message();
}
});

for (const auto &[ref, items] : uninstalledLayers) {
auto pkgRef = package::Reference::parse(ref);
if (!pkgRef) {
Expand Down
2 changes: 2 additions & 0 deletions libs/linglong/src/linglong/package_manager/package_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <QObject>

#include <optional>
#include <chrono>

namespace linglong::service {

Expand Down Expand Up @@ -172,6 +173,7 @@ public

int lockFd{ -1 };
linglong::runtime::ContainerBuilder &containerBuilder;
std::chrono::steady_clock::time_point lockStart;
};

} // namespace linglong::service
Loading
Loading