Skip to content
Merged
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 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ TEST = \
BINARIES = \
dde-session-daemon \
dde-system-daemon \
dde-wallpaper-helper \
grub2 \
search \
backlight_helper \
Expand Down
57 changes: 30 additions & 27 deletions bin/dde-system-daemon/wallpaper.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package main

import (
"bytes"
"crypto/md5"
"errors"
"fmt"
"os"
Expand All @@ -23,11 +25,11 @@ import (
)

const maxCount = 20
const maxSize = 32 * 1024 * 1024
const wallPaperDir = "/var/cache/wallpapers/custom-wallpapers/"
const solidWallPaperPath = "/var/cache/wallpapers/custom-solidwallpapers/"
const solidPrefix = "solid::"
const polkitActionUserAdministration = "org.deepin.dde.accounts.user-administration"
const wallpaperHelperPath = "/usr/lib/deepin-daemon/dde-wallpaper-helper"

var wallPaperDirs = []string{
wallPaperDir,
Expand All @@ -41,7 +43,9 @@ const (

func checkPath(path string, dirs []string) string {
for _, dir := range dirs {
if strings.HasPrefix(path, dir) {
// 确保目录路径以分隔符结尾,防止 "user1" 匹配 "user10"
prefix := dir + string(filepath.Separator)
if path == dir || strings.HasPrefix(path, prefix) {
return dir
}
}
Expand Down Expand Up @@ -210,6 +214,21 @@ func (d *Daemon) checkAuth(sender dbus.Sender) error {
return checkAuth(polkitActionUserAdministration, string(sender))
}

// readWallpaperSourceAsUser reads the wallpaper source file with the target
// user's privileges. It executes dde-wallpaper-helper via runuser so the
// kernel enforces the target user's file permissions, and the helper opens
// the file atomically with O_NOFOLLOW to prevent TOCTOU races.
func readWallpaperSourceAsUser(username string, file string) ([]byte, error) {
cmd := exec.Command("runuser", "-u", username, "--", wallpaperHelperPath, file)
var stderr bytes.Buffer
cmd.Stderr = &stderr
src, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("permission denied, %s is not allowed to read this file:%s: %s", username, file, strings.TrimSpace(stderr.String()))
}
return src, nil
}

func (d *Daemon) SaveCustomWallPaper(sender dbus.Sender, username string, file string) (string, *dbus.Error) {
var err error
var isSolid bool = false
Expand All @@ -219,18 +238,7 @@ func (d *Daemon) SaveCustomWallPaper(sender dbus.Sender, username string, file s
file = strings.TrimPrefix(file, solidPrefix)
isSolid = true
}
info, err := os.Stat(file)
if err != nil {
logger.Warning(err)
return "", dbusutil.ToError(err)
}

if info.Size() > maxSize {
err = fmt.Errorf("file size %d > %d", info.Size(), maxSize)
logger.Warning(err)
return "", dbusutil.ToError(err)
}
dirs, _ := GetUserDirs(username)
dirs, err := GetUserDirs(username)

if err != nil {
logger.Warning(err)
Expand All @@ -253,7 +261,13 @@ func (d *Daemon) SaveCustomWallPaper(sender dbus.Sender, username string, file s
err = fmt.Errorf("%s not allowed to set %s wallpaper", user.Username, username)
return "", dbusutil.ToError(err)
}
md5sum, _ := dutils.SumFileMd5(file)

src, err := readWallpaperSourceAsUser(username, file)
if err != nil {
logger.Warning(err)
return "", dbusutil.ToError(err)
}
md5sum := fmt.Sprintf("%x", md5.Sum(src))

var prefix string
if isSolid {
Expand All @@ -279,17 +293,6 @@ func (d *Daemon) SaveCustomWallPaper(sender dbus.Sender, username string, file s
if dutils.IsFileExist(destFile) {
return destFile, nil
}
src, err := exec.Command("runuser", []string{
"-u",
username,
"--",
"cat",
file,
}...).Output()
if err != nil {
err = fmt.Errorf("permission denied, %s is not allowed to read this file:%s", username, file)
return "", dbusutil.ToError(err)
}

err = os.WriteFile(destFile, src, 0644)
if err != nil {
Expand Down
55 changes: 55 additions & 0 deletions bin/dde-wallpaper-helper/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package main

import (
"fmt"
"io"
"os"

"golang.org/x/sys/unix"
)

// maxSize matches the limit enforced by dde-system-daemon's SaveCustomWallPaper.
const maxSize = 32 * 1024 * 1024

func main() {
if len(os.Args) != 2 {
_, _ = fmt.Fprintln(os.Stderr, "usage: dde-wallpaper-helper <file>")
os.Exit(1)
}

file := os.Args[1]

// Open with O_NOFOLLOW to atomically reject symlinks — this is the
// core TOCTOU defence. All subsequent operations use the returned fd.
fd, err := unix.Open(file, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
f := os.NewFile(uintptr(fd), file)
defer f.Close()

info, err := f.Stat()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if !info.Mode().IsRegular() {
_, _ = fmt.Fprintf(os.Stderr, "file %s is not a regular file\n", file)
os.Exit(1)
}
if info.Size() > maxSize {
_, _ = fmt.Fprintf(os.Stderr, "file size %d exceeds limit %d\n", info.Size(), maxSize)
os.Exit(1)
}

_, err = io.Copy(os.Stdout, f)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Loading