Skip to content

Commit 01c68af

Browse files
committed
Merge remote-tracking branch 'origin/pr/551'
* origin/pr/551: bind-dirs: fix permissions on $fso_ro bind-dirs: add x-gvfs-hide mount option to bind dirs This allows to hide mountpoints from Thunar sidebar (happens when bind mounting a file or dir in $HOME). custom-persist: prevent mount units from starting instead of bind mounting When disabling persistent /home or /usr/local, custom-persist was using a systemd drop-in to override the What= option and set it to the same value as the Where= one. This bind mount is unnecessary and was causing trouble when bind mounting other resources in /home or /usr/local. Instead, a ConditionPathExists= option is added to control whether this mount happens. custom-persist: pre-create parents with correct ownership When using custom-persist to pre-create the resource before bind mounting it, we might have to create its parents too. That was done using mkdir --parents that was causing parents to be created with root:root ownership which can leads to errors if, for example, a user wants to bind mount a directory inside its home dir. With this fix, parents are created with the same ownership as the resource. bind-dirs: fix /rw/home and /rw/usrlocal initialization from template files custom-persist: handle mounts from /rw/home and /rw/usrlocal Custom persist disables /home and /usr/local persistence by default but a user may want to bind mount a file or a directory in one of those locations without mounting the whole directories. For example, we should be able to mount /home/user/.ssh/ but keep the rest of /home/user non-persistent. With this fix, bind dirs detects when an object is located under /home or /usr/local and will look in the associated /rw/home or /rw/usrlocal instead of /rw/bind-dirs. If needed, custom-persist will pre-create the objects in the same location. custom-persist: prefer objets pre-creation in /rw This commit changes the files and dirs pre-creation path. Instead of pre-create files and dirs directly on the RO file system and let bind_dirs() function populate /rw/bind-dirs, custom-persist creates objects in /rw/bind-dirs like a regular user would do. custom-persist: files and directory auto-creation The support of metadata has been added to the custom-persist feature to allow automatic creation of files and directories declared through this feature. A type (file|dir), user, group and file mode must be specified before the path declaration. fix: bind-dirs should create files parent directories if they don't exist fix under_systemd function on debian Read command name in /proc custom-persist: init.d compatibility if the current VM is not under systemD we need to mount /home and /usr/local explicitly custom-persist: user suspend modules blacklist custom-persist: do not read user rc.local scripts when the feature is enabled custom-persist: disable user firewall rules when custom persist is enabled custom-persist: disable /home and /usr/local mounts If not explicitly configured, /rw/home and /rw/usrlocal must not be bind mounted to /home and /usr/local. Instead, the original /home and /usr/local is mounted. SystemD drop-ins are used to override the resource to mount (What= option in unit) custom-persist: mount binds configured in qubes-db Config is read from qubes database and every bind directory is mounted excepted /home and /usr/local which need to be handled differently custom-persist: systemd mount units for /home and /usr/local and services start dependencies The custom-persist feature should disable /home and /usr/local mounts by default. To do this, we can use SystemD drop-ins which requires to remove fstab entries and convert them to regular SystemD units as drop-ins does not seem to work with units generated by systemd-fstab-generator. Mount command in mount_dirs.sh is not required anymore and need to be deleted as it causes issues. Instead, a we can use SystemD unit options to ensure /home and /usr/local are mounted before loading user bind dirs custom-persist: ignore /rw/config bind-dirs if custom-persist enabled When the custom-persist feature is enabled, we no longer need to worry about the bind directories configured in /rw/config/qubes-bind-dirs.d. Pull request description: This PR adds a new feature ``custom-persist`` described in QubesOS/qubes-issues#1006
2 parents 529e8b2 + cc84ec6 commit 01c68af

File tree

17 files changed

+210
-30
lines changed

17 files changed

+210
-30
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ install-systemd: install-init
166166
install -m 0644 vm-systemd/xendriverdomain.service $(DESTDIR)/etc/systemd/system/
167167
install -m 0644 vm-systemd/80-qubes-vif.link $(DESTDIR)$(SYSLIBDIR)/systemd/network/
168168
install -m 0644 vm-systemd/30_resolved-no-mdns-or-llmnr.conf $(DESTDIR)$(SYSLIBDIR)/systemd/resolved.conf.d/
169+
install -m 0644 vm-systemd/home.mount $(DESTDIR)$(SYSLIBDIR)/systemd/system/
170+
install -m 0644 vm-systemd/usr-local.mount $(DESTDIR)$(SYSLIBDIR)/systemd/system/
169171

170172
.PHONY: install-sysvinit
171173
install-sysvinit: install-init

debian/qubes-core-agent.install

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ lib/systemd/system/org.cups.cupsd.service.d/30_qubes.conf
8989
lib/systemd/system/org.cups.cupsd.socket.d/30_qubes.conf
9090
lib/systemd/system/polkit.service.d/30_qubes.conf
9191
lib/systemd/system/dev-xvdc1-swap.service
92+
lib/systemd/system/qubes-bind-dirs.service
9293
lib/systemd/system/qubes-early-vm-config.service
9394
lib/systemd/system/qubes-misc-post.service
9495
lib/systemd/system/qubes-mount-dirs.service
@@ -114,6 +115,8 @@ lib/systemd/system/systemd-nsresourced.socket.d/30_qubes.conf
114115
lib/systemd/system/systemd-userdbd.service.d/30_qubes.conf
115116
lib/systemd/system/systemd-userdbd.socket.d/30_qubes.conf
116117
lib/systemd/resolved.conf.d/30_resolved-no-mdns-or-llmnr.conf
118+
lib/systemd/system/home.mount
119+
lib/systemd/system/usr-local.mount
117120
usr/lib/sysctl.d/20-qubes-core.conf
118121
usr/lib/systemd/user/tracker-extract-3.service.d/30_qubes.conf
119122
usr/lib/systemd/user/tracker-miner-fs-3.service.d/30_qubes.conf

filesystem/fstab

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
/dev/mapper/dmroot / ext4 defaults,discard,noatime 1 1
88
/dev/xvdb /rw auto noauto,defaults,discard,nosuid,nodev 1 2
9-
/rw/home /home none noauto,bind,defaults,nosuid,nodev 0 0
10-
/rw/usrlocal /usr/local none noauto,bind,defaults 0 0
119
/dev/xvdc1 swap swap defaults 0 0
1210
tmpfs /dev/shm tmpfs defaults,size=1G 0 0
1311
devpts /dev/pts devpts gid=5,mode=620 0 0

init/functions

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ qsvc() {
2323
}
2424

2525
under_systemd() {
26-
pidof systemd >/dev/null 2>&1
26+
local init_command_name
27+
read -r init_command_name < /proc/1/comm
28+
[ "$init_command_name" = "systemd" ]
2729
}
2830

2931
systemd_version_changed() {
@@ -57,6 +59,10 @@ is_appvm() {
5759
[ "$(qubes_vm_type)" = "AppVM" ]
5860
}
5961

62+
is_custom_persist_enabled() {
63+
[ -f "/var/run/qubes-service/custom-persist" ]
64+
}
65+
6066
is_proxyvm() {
6167
[ "$(qubes_vm_type)" = "ProxyVM" ]
6268
}
@@ -246,3 +252,13 @@ initialize_home() {
246252
for waitpid in $waitpids ; do wait "$waitpid" ; done ; waitpids=
247253
done
248254
}
255+
256+
disable_persistent_home() {
257+
echo "Disabling persistent /home"
258+
touch /var/run/qubes/disable_persistent_home_dir
259+
}
260+
261+
disable_persistent_usrlocal() {
262+
echo "Disabling persistent /usr/local"
263+
touch /var/run/qubes/disable_persistent_usrlocal_dir
264+
}

qubes-rpc/prepare-suspend

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ MODULES_BLACKLIST=""
1616
if [ -r /etc/qubes-suspend-module-blacklist ]; then
1717
MODULES_BLACKLIST="$MODULES_BLACKLIST $(grep -v '^#' /etc/qubes-suspend-module-blacklist)"
1818
fi
19-
if [ -r /rw/config/suspend-module-blacklist ]; then
19+
if [ -r /rw/config/suspend-module-blacklist ] && ! is_custom_persist_enabled; then
2020
MODULES_BLACKLIST="$MODULES_BLACKLIST $(grep -v '^#' /rw/config/suspend-module-blacklist)"
2121
fi
2222

qubesagent/firewall.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,15 @@ def get_connected_ips(self, family):
8686
return []
8787
return ips.decode().split()
8888

89+
def is_custom_persist_enabled(self) -> bool:
90+
"""Check if the feature custom-persist is enabled on the current VM"""
91+
return os.path.isfile('/var/run/qubes-service/custom-persist')
92+
8993
def run_firewall_dir(self):
9094
"""Run scripts dir contents, before user script"""
91-
script_dir_paths = ['/etc/qubes/qubes-firewall.d',
92-
'/rw/config/qubes-firewall.d']
95+
script_dir_paths = ['/etc/qubes/qubes-firewall.d']
96+
if not self.is_custom_persist_enabled():
97+
script_dir_paths.append('/rw/config/qubes-firewall.d')
9398
for script_dir_path in script_dir_paths:
9499
if not os.path.isdir(script_dir_path):
95100
continue
@@ -328,7 +333,8 @@ def main(self):
328333
self.terminate_requested = False
329334
self.init()
330335
self.run_firewall_dir()
331-
self.run_user_script()
336+
if not self.is_custom_persist_enabled():
337+
self.run_user_script()
332338
self.sd_notify('READY=1')
333339
self.qdb.watch('/qubes-firewall/')
334340
self.qdb.watch('/connected-ips')

rpm_spec/core-agent.spec.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,8 @@ The Qubes core startup configuration for SystemD init.
12631263
%defattr(-,root,root,-)
12641264
/etc/systemd/system/xendriverdomain.service
12651265
%_unitdir/dev-xvdc1-swap.service
1266+
%_unitdir/home.mount
1267+
%_unitdir/qubes-bind-dirs.service
12661268
%_unitdir/qubes-misc-post.service
12671269
%_unitdir/qubes-mount-dirs.service
12681270
%_unitdir/qubes-rootfs-resize.service
@@ -1274,6 +1276,7 @@ The Qubes core startup configuration for SystemD init.
12741276
%_unitdir/qubes-sync-time.timer
12751277
12761278
%_unitdir/qubes-updates-proxy-forwarder.socket
1279+
%_unitdir/usr-local.mount
12771280
%{_unitdir}-preset/%qubes_preset_file
12781281
%_modulesloaddir/qubes-core.conf
12791282
%_unitdir/abrtd.service.d/30_qubes.conf

vm-systemd/75-qubes-vm.preset

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ enable qubes-network.service
9696
enable qubes-network-uplink.service
9797
enable qubes-qrexec-agent.service
9898
enable qubes-mount-dirs.service
99+
enable qubes-bind-dirs.service
99100
enable qubes-rootfs-resize.service
100101
enable qubes-firewall.service
101102
enable qubes-meminfo-writer.service

vm-systemd/NetworkManager.service.d/30_qubes.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[Unit]
22
ConditionPathExists=/var/run/qubes-service/network-manager
33
# For /rw
4-
After=qubes-mount-dirs.service
4+
After=qubes-bind-dirs.service
55
# For /var/run/qubes-service
66
After=qubes-sysinit.service
77
# For configuration of qubes-provided interfaces

vm-systemd/bind-dirs.sh

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ shopt -s nullglob dotglob
2929
# shellcheck source=init/functions
3030
source /usr/lib/qubes/init/functions
3131

32+
readonly DEFAULT_RW_BIND_DIR="/rw/bind-dirs"
33+
3234
prerequisite() {
3335
if is_fully_persistent ; then
3436
echo "No TemplateBasedVM/DisposableVM detected. Exiting."
@@ -37,7 +39,7 @@ prerequisite() {
3739
}
3840

3941
init() {
40-
[ -n "$rw_dest_dir" ] || rw_dest_dir="/rw/bind-dirs"
42+
[ -n "$rw_dest_dir" ] || rw_dest_dir="$DEFAULT_RW_BIND_DIR"
4143
[ -n "$symlink_level_max" ] || symlink_level_max="10"
4244
mkdir --parents "$rw_dest_dir"
4345
}
@@ -49,6 +51,21 @@ legacy() {
4951
true
5052
}
5153

54+
rw_from_ro() {
55+
ro="$1"
56+
# special cases for files/dirs in /home or /usr/local
57+
if [[ "$ro" =~ ^/home/ ]]; then
58+
# use /rw/home for /home/... binds
59+
rw="/rw${ro}"
60+
elif [[ "$ro" =~ ^/usr/local/ ]]; then
61+
# use /rw/usrlocal for /usr/local/... binds
62+
rw="/rw/usrlocal/$(echo "$ro" | cut -d/ -f4-)"
63+
else
64+
[ -z "$rw_dest_dir" ] && rw="${DEFAULT_RW_BIND_DIR}${ro}" || rw="${rw_dest_dir}${ro}"
65+
fi
66+
echo "$rw"
67+
}
68+
5269
bind_dirs() {
5370
## legend
5471
## fso: file system object
@@ -77,7 +94,7 @@ bind_dirs() {
7794
done
7895

7996
true "fso_ro: $fso_ro"
80-
fso_rw="${rw_dest_dir}${fso_ro}"
97+
fso_rw="$(rw_from_ro "$fso_ro")"
8198

8299
# Make sure fso_ro is not mounted.
83100
umount "$fso_ro" 2> /dev/null || true
@@ -90,14 +107,22 @@ bind_dirs() {
90107
if [ -d "$fso_rw" ] || [ -f "$fso_rw" ]; then
91108
if [ ! -e "$fso_ro" ]; then
92109
## Create empty file or directory if path exists in /rw to allow to bind mount none existing files/dirs.
93-
test -d "$fso_rw" && mkdir --parents "$fso_ro"
94-
test -f "$fso_rw" && touch "$fso_ro"
110+
# shellcheck disable=SC2046
111+
test -d "$fso_rw" && mk_parent_dirs "$fso_ro" $(stat --printf "%U %G" "$fso_rw")
112+
if [ -f "$fso_rw" ]; then
113+
parent_directory="$(dirname "$fso_ro")"
114+
# shellcheck disable=SC2046
115+
test -d "$parent_directory" || mk_parent_dirs "$parent_directory" $(stat --printf "%U %G" "$fso_rw")
116+
touch "$fso_ro"
117+
fi
95118
fi
96119
else
97120
if [ -d "$fso_ro" ] || [ -f "$fso_ro" ]; then
98121
## Initially copy over data directories to /rw if rw directory does not exist.
99-
echo "Initializing $rw_dest_dir with files from $fso_ro" >&2
100-
cp --archive --recursive --parents "$fso_ro" "$rw_dest_dir"
122+
echo "Initializing $fso_rw with files from $fso_ro" >&2
123+
parent_directory="$(dirname "$fso_rw")"
124+
test -d "$parent_directory" || mkdir --parents "$parent_directory"
125+
cp --archive --recursive "$fso_ro" "$fso_rw"
101126
else
102127
echo "$fso_ro is neither a directory nor a file and the path does not exist below /rw, skipping."
103128
continue
@@ -106,10 +131,23 @@ bind_dirs() {
106131

107132
# Bind the fso.
108133
echo "Bind mounting $fso_rw onto $fso_ro" >&2
109-
mount --bind "$fso_rw" "$fso_ro"
134+
mount --bind -o x-gvfs-hide "$fso_rw" "$fso_ro"
110135
done
111136
}
112137

138+
mk_parent_dirs() {
139+
local target="$1"
140+
local owner="$2"
141+
local group="$3"
142+
local depth="$4"
143+
[[ "$depth" -gt 100 ]] && echo "Maximum recursion depth reached" >&2 && return 1
144+
[ -e "$target" ] && return 0
145+
mk_parent_dirs "$(dirname "$target")" "$owner" "$group" "$(( depth + 1 ))" || return 1
146+
mkdir "$target" || return 1
147+
chown "$owner":"$group" "$target" || return 1
148+
return 0
149+
}
150+
113151
main() {
114152
prerequisite "$@"
115153
init "$@"
@@ -118,7 +156,12 @@ main() {
118156
}
119157

120158
binds=()
121-
for source_folder in /usr/lib/qubes-bind-dirs.d /etc/qubes-bind-dirs.d /rw/config/qubes-bind-dirs.d ; do
159+
sources=( "/usr/lib/qubes-bind-dirs.d" "/etc/qubes-bind-dirs.d" )
160+
if [ ! -f "/var/run/qubes-service/custom-persist" ]; then
161+
sources+=( "/rw/config/qubes-bind-dirs.d" )
162+
fi
163+
164+
for source_folder in "${sources[@]}"; do
122165
true "source_folder: $source_folder"
123166
if [ ! -d "$source_folder" ]; then
124167
continue
@@ -130,6 +173,60 @@ for source_folder in /usr/lib/qubes-bind-dirs.d /etc/qubes-bind-dirs.d /rw/confi
130173
done
131174
done
132175

176+
# read binds in QubesDB if custom-persist feature is enabled
177+
if is_custom_persist_enabled; then
178+
while read -r qubes_persist_entry; do
179+
[[ "$qubes_persist_entry" =~ =\ (.*)$ ]] || continue
180+
target="${BASH_REMATCH[1]}"
181+
182+
# if the first char is not a slash, options should be extracted from
183+
# the value
184+
if [[ "$target" != /* ]]; then
185+
resource_type="$(echo "$target" | cut -d':' -f1)"
186+
owner="$(echo "$target" | cut -d':' -f2)"
187+
group="$(echo "$target" | cut -d':' -f3)"
188+
mode="$(echo "$target" | cut -d':' -f4)"
189+
path="$(echo "$target" | cut -d':' -f5-)"
190+
191+
if [ -z "$path" ] || [[ "$path" != /* ]]; then
192+
echo "Skipping invalid custom-persist value '${target}'" >&2
193+
continue
194+
fi
195+
196+
rw_path="$(rw_from_ro "${path}")"
197+
# create resource if it does not exist
198+
if ! [ -e "${path}" ] && ! [ -e "$rw_path" ]; then
199+
if [ "$resource_type" = "file" ]; then
200+
# for files, we need to create parent directories
201+
parent_directory="$(dirname "$rw_path")"
202+
echo "custom-persist: pre-creating file ${rw_path} with rights ${owner}:${group} ${mode}"
203+
if [ ! -d "$parent_directory" ]; then
204+
if ! mk_parent_dirs "$parent_directory" "$owner" "$group"; then
205+
echo "Unable to create ${rw_path} parent dirs, skipping"
206+
continue
207+
fi
208+
fi
209+
touch "${rw_path}"
210+
elif [ "$resource_type" = "dir" ]; then
211+
echo "custom-persist: pre-creating directory ${rw_path} with rights ${owner}:${group} ${mode}"
212+
if ! mk_parent_dirs "$rw_path" "$owner" "$group"; then
213+
echo "Unable to create ${rw_path} parent dirs, skipping"
214+
continue
215+
fi
216+
else
217+
echo "Invalid entry ${target}, skipping"
218+
continue
219+
fi
220+
chown "$owner":"$group" "${rw_path}"
221+
chmod "$mode" "${rw_path}"
222+
fi
223+
target="$path"
224+
fi
225+
[[ "$target" =~ ^(\/home|\/usr\/local)$ ]] && continue
226+
binds+=( "$target" )
227+
done <<< "$(qubesdb-multiread /persist/)"
228+
fi
229+
133230
main "$@"
134231

135232
true "OK: END."

vm-systemd/home.mount

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Unit]
2+
ConditionPathExists=!/var/run/qubes/disable_persistent_home_dir
3+
4+
[Mount]
5+
What=/rw/home
6+
Where=/home
7+
Type=none
8+
Options=noauto,bind,defaults,nosuid,nodev

vm-systemd/misc-post.sh

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ if [ -n "$(ls -A /usr/local/lib 2>/dev/null)" ] || \
1111
ldconfig
1212
fi
1313

14-
for rc in /rw/config/rc.local.d/*.rc /rw/config/rc.local; do
15-
[ -f "${rc}" ] || continue
16-
[ -x "${rc}" ] || continue
17-
"${rc}"
18-
done
14+
if ! is_custom_persist_enabled; then
15+
for rc in /rw/config/rc.local.d/*.rc /rw/config/rc.local; do
16+
[ -f "${rc}" ] || continue
17+
[ -x "${rc}" ] || continue
18+
"${rc}"
19+
done
20+
fi
1921
unset rc

vm-systemd/mount-dirs.sh

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh
1+
#!/bin/bash
22

33
# Source Qubes library.
44
# shellcheck source=init/functions
@@ -10,9 +10,31 @@ set -e
1010
if [ -e /dev/xvdb ] ; then mount /rw ; fi
1111
/usr/lib/qubes/init/setup-rw.sh
1212

13-
initialize_home "/rw/home" ifneeded
14-
echo "Mounting /rw/home onto /home" >&2
15-
mount /home
16-
echo "Mounting /rw/usrlocal onto /usr/local" >&2
17-
mount /usr/local
18-
/usr/lib/qubes/init/bind-dirs.sh
13+
if is_custom_persist_enabled; then
14+
mount_home=false
15+
mount_usr_local=false
16+
17+
while read -r qubes_persist_entry; do
18+
[[ "$qubes_persist_entry" =~ \=\ /home$ ]] && mount_home=true
19+
[[ "$qubes_persist_entry" =~ \=\ /usr/local$ ]] && mount_usr_local=true
20+
done <<< "$(qubesdb-multiread /persist/)"
21+
else
22+
mount_home=true
23+
mount_usr_local=true
24+
fi
25+
26+
if $mount_home; then
27+
initialize_home "/rw/home" ifneeded
28+
under_systemd || mount -o noauto,bind,defaults,nosuid,nodev /rw/home /home
29+
else
30+
under_systemd && disable_persistent_home
31+
initialize_home "/home" unconditionally
32+
fi
33+
34+
if $mount_usr_local; then
35+
under_systemd || mount -o noauto,bind,defaults /rw/usrlocal /usr/local
36+
else
37+
under_systemd && disable_persistent_usrlocal
38+
fi
39+
40+
under_systemd && systemctl daemon-reload || exit 0

0 commit comments

Comments
 (0)