summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrian Vovk <adrianvovk@gmail.com>2024-01-10 04:06:35 +0100
committerLuca Boccassi <bluca@debian.org>2024-02-19 12:18:11 +0100
commita4d72746c776f820a440d72eaadd49ad158e10dc (patch)
tree64ed01694861da09105346167e80b56aaa1e74af
parenthomework: Reconcile blob directories (diff)
downloadsystemd-a4d72746c776f820a440d72eaadd49ad158e10dc.tar.xz
systemd-a4d72746c776f820a440d72eaadd49ad158e10dc.zip
homework: Handle Update & Create w/ blob dir
Introduces new extended variants of the various incarnations of Create and Update, which take a map of filenames to FDs. This map is then used to populate the bulk directory. FDs are used to prevent the client from abusing homed's blob directory permissions (everything is made world-readable by homed) to open files that they normally aren't allowed to open. Passing along an FD ensures that the client has read access to the file it wants homed to make world-readable. Internally, homework uses the map to overwrite the system blob dir. Later, homework's existing blob dir reconciliation logic will propagate the new contents from the system blob dir into the embedded blob dir
-rw-r--r--man/org.freedesktop.home1.xml52
-rw-r--r--src/home/home-util.c3
-rw-r--r--src/home/home-util.h3
-rw-r--r--src/home/homed-bus.c73
-rw-r--r--src/home/homed-bus.h4
-rw-r--r--src/home/homed-home-bus.c34
-rw-r--r--src/home/homed-home-bus.h2
-rw-r--r--src/home/homed-home.c100
-rw-r--r--src/home/homed-home.h5
-rw-r--r--src/home/homed-manager-bus.c60
-rw-r--r--src/home/homework-blob.c58
-rw-r--r--src/home/homework-blob.h2
-rw-r--r--src/home/homework.c63
-rw-r--r--src/home/org.freedesktop.home1.conf12
-rw-r--r--src/home/user-record-util.c105
-rw-r--r--src/home/user-record-util.h7
16 files changed, 534 insertions, 49 deletions
diff --git a/man/org.freedesktop.home1.xml b/man/org.freedesktop.home1.xml
index b1bb6587dc..8ac3d96086 100644
--- a/man/org.freedesktop.home1.xml
+++ b/man/org.freedesktop.home1.xml
@@ -72,6 +72,9 @@ node /org/freedesktop/home1 {
RegisterHome(in s user_record);
UnregisterHome(in s user_name);
CreateHome(in s user_record);
+ CreateHomeEx(in s user_record,
+ in a{sh} blobs,
+ in t flags);
RealizeHome(in s user_name,
in s secret);
RemoveHome(in s user_name);
@@ -81,6 +84,9 @@ node /org/freedesktop/home1 {
AuthenticateHome(in s user_name,
in s secret);
UpdateHome(in s user_record);
+ UpdateHomeEx(in s user_record,
+ in a{sh} blobs,
+ in t flags);
ResizeHome(in s user_name,
in t size,
in s secret);
@@ -151,6 +157,8 @@ node /org/freedesktop/home1 {
<variablelist class="dbus-method" generated="True" extra-ref="CreateHome()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="CreateHomeEx()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="RealizeHome()"/>
<variablelist class="dbus-method" generated="True" extra-ref="RemoveHome()"/>
@@ -161,6 +169,8 @@ node /org/freedesktop/home1 {
<variablelist class="dbus-method" generated="True" extra-ref="UpdateHome()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="UpdateHomeEx()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="ResizeHome()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ChangePasswordHome()"/>
@@ -265,6 +275,19 @@ node /org/freedesktop/home1 {
the user record locally and creates a home directory matching it, depending on the settings specified
in the record in combination with local configuration.</para>
+ <para><function>CreateHomeEx()</function> is like <function>CreateHome()</function>, but it allows the
+ home directory to be created with a pre-populated blob directory (see
+ <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink> for more info).
+ This can be done via the dictionary passed as the <varname>blobs</varname> argument to this method: the values
+ are open file descriptors to regular files, and the keys are the filenames that should contain their respective
+ file's data in the blob directory. Note that for security reasons, the file descriptors passed into this method
+ must have enough privileges to read their target file and thus cannot be <literal>O_PATH</literal>; this
+ is done to ensure the caller is actually permitted to read the file they are asking to publish in the
+ blob directories. If the user record passed as the first argument contains a <literal>blobManifest</literal>
+ field it will be enforced; otherwise, a <literal>blobManifest</literal> field will be generated and inserted
+ into the record. The <varname>flags</varname> argument may be used for future expansion, but for now
+ pass 0.</para>
+
<para><function>RealizeHome()</function> creates a home directory whose user record is already
registered locally. This takes a user name plus a user record consisting only of the
<literal>secret</literal> section. Invoking <function>RegisterHome()</function> followed by
@@ -308,6 +331,14 @@ node /org/freedesktop/home1 {
as the storage resized using <function>ResizeHome()</function>. This method is equivalent to
<function>Update()</function> on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
+ <para><function>UpdateHomeEx()</function> is like <function>UpdateHome()</function>, but it allows for
+ changes to the blob directory (see <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob
+ Directories</ulink> for more info). The <varname>blobs</varname> argument works in the same way as
+ <function>CreateHomeEx()</function>, so check there for details. The new blob directory contents passed into
+ this method will completely replace the user's existing blob directory. The <varname>flags</varname> argument
+ may be used for future expansion, but for now pass 0. This method is equivalent to <function>UpdateEx()</function>
+ on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
+
<para><function>ResizeHome()</function> resizes the storage associated with a user record. Takes a user
name, a disk size in bytes and a user record consisting only of the <literal>secret</literal> section
as argument. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to the
@@ -438,6 +469,9 @@ node /org/freedesktop/home1/home {
Fixate(in s secret);
Authenticate(in s secret);
Update(in s user_record);
+ UpdateEx(in s user_record,
+ in a{sh} blobs,
+ in t flags);
Resize(in t size,
in s secret);
ChangePassword(in s new_secret,
@@ -504,6 +538,8 @@ node /org/freedesktop/home1/home {
<variablelist class="dbus-method" generated="True" extra-ref="Update()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="UpdateEx()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="Resize()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ChangePassword()"/>
@@ -540,11 +576,11 @@ node /org/freedesktop/home1/home {
<para><function>Activate()</function>, <function>ActivateIfReferenced()</function>,
<function>Deactivate()</function>, <function>Unregister()</function>, <function>Realize()</function>,
<function>Remove()</function>, <function>Fixate()</function>, <function>Authenticate()</function>,
- <function>Update()</function>, <function>Resize()</function>, <function>ChangePassword()</function>,
- <function>Lock()</function>, <function>Unlock()</function>, <function>Acquire()</function>,
- <function>Ref()</function>, <function>RefUnrestricted()</function>, <function>Release()</function>,
- <function>InhibitSuspend()</function> operate like their matching counterparts on the
- <classname>org.freedesktop.home1.Manager</classname> interface (see above). The main difference is that
+ <function>Update()</function>, <function>UpdateEx()</function>, <function>Resize()</function>,
+ <function>ChangePassword()</function>, <function>Lock()</function>, <function>Unlock()</function>,
+ <function>Acquire()</function>, <function>Ref()</function>, <function>RefUnrestricted()</function>,
+ <function>Release()</function>, <function>InhibitSuspend()</function> operate like their matching counterparts
+ on the <classname>org.freedesktop.home1.Manager</classname> interface (see above). The main difference is that
they are methods of the home directory objects, and hence carry no additional user name
parameter. Which of the two flavors of methods to call depends on the handles to the user known on the
client side: if only the user name is known, it's preferable to use the methods on the manager object
@@ -575,11 +611,13 @@ node /org/freedesktop/home1/home {
<title>History</title>
<refsect2>
<title>The Manager Object</title>
- <para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function> wer added in version 256.</para>
+ <para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function>,
+ <function>CreateHomeEx()</function>, and <function>UpdateHomeEx()</function> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Home Objects</title>
- <para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function> and <function>RefUnrestricted()</function> were added in version 256.</para>
+ <para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function>, <function>RefUnrestricted()</function>, and
+ <function>UpdateEx()</function> were added in version 256.</para>
</refsect2>
</refsect1>
diff --git a/src/home/home-util.c b/src/home/home-util.c
index 9c9c0ff78a..973523652d 100644
--- a/src/home/home-util.c
+++ b/src/home/home-util.c
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dns-domain.h"
+#include "fd-util.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "memory-util.h"
@@ -9,6 +10,8 @@
#include "strv.h"
#include "user-util.h"
+DEFINE_HASH_OPS_FULL(blob_fd_hash_ops, char, path_hash_func, path_compare, free, void, close_fd_ptr);
+
bool suitable_user_name(const char *name) {
/* Checks whether the specified name is suitable for management via homed. Note that client-side
diff --git a/src/home/home-util.h b/src/home/home-util.h
index ead8f5637e..f2e5787a54 100644
--- a/src/home/home-util.h
+++ b/src/home/home-util.h
@@ -5,6 +5,7 @@
#include "sd-bus.h"
+#include "hash-funcs.h"
#include "time-util.h"
#include "user-record.h"
@@ -20,6 +21,8 @@
/* This should be 83% right now, i.e. 100 of (100 + 20). Let's protect us against accidental changes. */
assert_cc(USER_DISK_SIZE_DEFAULT_PERCENT == 83U);
+extern const struct hash_ops blob_fd_hash_ops;
+
bool suitable_user_name(const char *name);
int suitable_realm(const char *realm);
int suitable_image_path(const char *path);
diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c
index 24b421a58c..cbd84c231c 100644
--- a/src/home/homed-bus.c
+++ b/src/home/homed-bus.c
@@ -1,6 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "fd-util.h"
+#include "home-util.h"
#include "homed-bus.h"
+#include "missing_fcntl.h"
+#include "stat-util.h"
#include "strv.h"
int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) {
@@ -64,3 +68,72 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U
*ret = TAKE_PTR(hr);
return 0;
}
+
+int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error) {
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ /* We want to differentiate between blobs being NULL (not passed at all)
+ * and empty (passed from dbus, but it was empty) */
+ r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, 'a', "{sh}");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ _cleanup_free_ char *filename = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ const char *_filename = NULL;
+ int _fd, flags;
+
+ r = sd_bus_message_read(m, "{sh}", &_filename, &_fd);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ filename = strdup(_filename);
+ if (!filename)
+ return -ENOMEM;
+
+ fd = fcntl(_fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ return -errno;
+
+ r = suitable_blob_filename(filename);
+ if (r < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid blob directory filename: %s", filename);
+
+ r = fd_verify_regular(fd);
+ if (r < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s is not a regular file", filename);
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ /* Refuse fds w/ unexpected flags set. In particular, we don't want to permit O_PATH FDs, since
+ * those don't actually guarentee that the client has access to the file. */
+ if ((flags & ~(O_ACCMODE|RAW_O_LARGEFILE)) != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s has unexpected flags set", filename);
+
+ r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
+ if (r < 0)
+ return r;
+ TAKE_PTR(filename); /* Ownership transferred to hashmap */
+ TAKE_FD(fd);
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(blobs);
+ return 0;
+}
diff --git a/src/home/homed-bus.h b/src/home/homed-bus.h
index 977679b10a..0660a5936c 100644
--- a/src/home/homed-bus.h
+++ b/src/home/homed-bus.h
@@ -3,8 +3,10 @@
#include "sd-bus.h"
-#include "user-record.h"
+#include "hashmap.h"
#include "json.h"
+#include "user-record.h"
int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error);
int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error);
+int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error);
diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c
index 81f0df4c95..f54de1f581 100644
--- a/src/home/homed-home-bus.c
+++ b/src/home/homed-home-bus.c
@@ -295,7 +295,7 @@ int bus_home_method_realize(
if (r == 0)
return 1; /* Will call us back */
- r = home_create(h, secret, error);
+ r = home_create(h, secret, NULL, 0, error);
if (r < 0)
return r;
@@ -416,7 +416,13 @@ int bus_home_method_authenticate(
return 1;
}
-int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) {
+int bus_home_method_update_record(
+ Home *h,
+ sd_bus_message *message,
+ UserRecord *hr,
+ Hashmap *blobs,
+ uint64_t flags,
+ sd_bus_error *error) {
int r;
assert(h);
@@ -427,6 +433,9 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
if (r < 0)
return r;
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+
r = home_verify_polkit_async(
h,
message,
@@ -438,7 +447,7 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
if (r == 0)
return 1; /* Will call us back */
- r = home_update(h, hr, error);
+ r = home_update(h, hr, blobs, flags, error);
if (r < 0)
return r;
@@ -458,6 +467,8 @@ int bus_home_method_update(
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+ uint64_t flags = 0;
Home *h = ASSERT_PTR(userdata);
int r;
@@ -467,7 +478,17 @@ int bus_home_method_update(
if (r < 0)
return r;
- return bus_home_method_update_record(h, message, hr, error);
+ if (endswith(sd_bus_message_get_member(message), "Ex")) {
+ r = bus_message_read_blobs(message, &blobs, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "t", &flags);
+ if (r < 0)
+ return r;
+ }
+
+ return bus_home_method_update_record(h, message, hr, blobs, flags, error);
}
int bus_home_method_resize(
@@ -892,6 +913,11 @@ const sd_bus_vtable home_vtable[] = {
SD_BUS_NO_RESULT,
bus_home_method_update,
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+ SD_BUS_METHOD_WITH_ARGS("UpdateEx",
+ SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+ SD_BUS_NO_RESULT,
+ bus_home_method_update,
+ SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
SD_BUS_METHOD_WITH_ARGS("Resize",
SD_BUS_ARGS("t", size, "s", secret),
SD_BUS_NO_RESULT,
diff --git a/src/home/homed-home-bus.h b/src/home/homed-home-bus.h
index a791c76312..9ba2cf1252 100644
--- a/src/home/homed-home-bus.h
+++ b/src/home/homed-home-bus.h
@@ -17,7 +17,7 @@ int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error
int bus_home_method_fixate(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_home_method_authenticate(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_home_method_update(sd_bus_message *message, void *userdata, sd_bus_error *error);
-int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, sd_bus_error *error);
+int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
int bus_home_method_resize(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_home_method_change_password(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_home_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/home/homed-home.c b/src/home/homed-home.c
index 7bb078ee2c..a487eb1fd1 100644
--- a/src/home/homed-home.c
+++ b/src/home/homed-home.c
@@ -55,7 +55,13 @@
assert_cc(HOME_UID_MIN <= HOME_UID_MAX);
assert_cc(HOME_USERS_MAX <= (HOME_UID_MAX - HOME_UID_MIN + 1));
-static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret);
+static int home_start_work(
+ Home *h,
+ const char *verb,
+ UserRecord *hr,
+ UserRecord *secret,
+ Hashmap *blobs,
+ uint64_t flags);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref);
@@ -738,7 +744,7 @@ static void home_fixate_finish(Home *h, int ret, UserRecord *hr) {
if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
- r = home_start_work(h, "activate", h->record, secret);
+ r = home_start_work(h, "activate", h->record, secret, NULL, 0);
if (r < 0) {
h->current_operation = operation_result_unref(h->current_operation, r, NULL);
home_set_state(h, _HOME_STATE_INVALID);
@@ -1178,10 +1184,17 @@ static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void
return 0;
}
-static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) {
- _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+static int home_start_work(
+ Home *h,
+ const char *verb,
+ UserRecord *hr,
+ UserRecord *secret,
+ Hashmap *blobs,
+ uint64_t flags) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *fdmap = NULL;
_cleanup_(erase_and_freep) char *formatted = NULL;
_cleanup_close_ int stdin_fd = -EBADF, stdout_fd = -EBADF;
+ _cleanup_free_ int *blob_fds = NULL;
pid_t pid = 0;
int r;
@@ -1209,6 +1222,37 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
return r;
}
+ if (blobs) {
+ const char *blob_filename = NULL;
+ void *fd_ptr;
+ size_t i = 0;
+
+ blob_fds = new(int, hashmap_size(blobs));
+ if (!blob_fds)
+ return -ENOMEM;
+
+ /* homework needs to be able to tell the difference between blobs being null
+ * (the fdmap field is completely missing) and it being empty (the field is an
+ * empty object) */
+ r = json_variant_new_object(&fdmap, NULL, 0);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH_KEY(fd_ptr, blob_filename, blobs) {
+ blob_fds[i] = PTR_TO_FD(fd_ptr);
+
+ r = json_variant_set_field_integer(&fdmap, blob_filename, i);
+ if (r < 0)
+ return r;
+
+ i++;
+ }
+
+ r = json_variant_set_field(&v, HOMEWORK_BLOB_FDMAP_FIELD, fdmap);
+ if (r < 0)
+ return r;
+ }
+
r = json_variant_format(v, 0, &formatted);
if (r < 0)
return r;
@@ -1225,8 +1269,9 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
r = safe_fork_full("(sd-homework)",
(int[]) { stdin_fd, stdout_fd, STDERR_FILENO },
- NULL, 0,
- FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
+ blob_fds, hashmap_size(blobs),
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_CLOEXEC_OFF|FORK_PACK_FDS|FORK_DEATHSIG_SIGTERM|
+ FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
@@ -1271,6 +1316,10 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
if (r < 0)
log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
+ r = setenv_systemd_log_level();
+ if (r < 0)
+ log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m");
+
/* Allow overriding the homework path via an environment variable, to make debugging
* easier. */
homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH;
@@ -1343,7 +1392,7 @@ static int home_fixate_internal(
assert(secret);
assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
- r = home_start_work(h, "inspect", h->record, secret);
+ r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
if (r < 0)
return r;
@@ -1392,7 +1441,7 @@ static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_sta
assert(secret);
assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
- r = home_start_work(h, "activate", h->record, secret);
+ r = home_start_work(h, "activate", h->record, secret, NULL, 0);
if (r < 0)
return r;
@@ -1444,7 +1493,7 @@ static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for
assert(secret);
assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
- r = home_start_work(h, "inspect", h->record, secret);
+ r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
if (r < 0)
return r;
@@ -1489,7 +1538,7 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
home_unpin(h); /* unpin so that we can deactivate */
- r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
+ r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL, NULL, 0);
if (r < 0)
/* Operation failed before it even started, reacquire pin fd, if state still dictates so */
home_update_pin_fd(h, _HOME_STATE_INVALID);
@@ -1526,7 +1575,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
return home_deactivate_internal(h, force, error);
}
-int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
+int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
int r;
assert(h);
@@ -1569,7 +1618,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
return r;
}
- r = home_start_work(h, "create", h->record, secret);
+ r = home_start_work(h, "create", h->record, secret, blobs, flags);
if (r < 0)
return r;
@@ -1599,7 +1648,7 @@ int home_remove(Home *h, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
}
- r = home_start_work(h, "remove", h->record, NULL);
+ r = home_start_work(h, "remove", h->record, NULL, NULL, 0);
if (r < 0)
return r;
@@ -1643,6 +1692,8 @@ static int home_update_internal(
const char *verb,
UserRecord *hr,
UserRecord *secret,
+ Hashmap *blobs,
+ uint64_t flags,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
@@ -1666,6 +1717,15 @@ static int home_update_internal(
secret = saved_secret;
}
+ if (blobs) {
+ const char *failed = NULL;
+ r = user_record_ensure_blob_manifest(hr, blobs, &failed);
+ if (r == -EINVAL)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
+ }
+
r = manager_verify_user_record(h->manager, hr);
switch (r) {
@@ -1708,14 +1768,14 @@ static int home_update_internal(
return sd_bus_error_set(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing.");
}
- r = home_start_work(h, verb, new_hr, secret);
+ r = home_start_work(h, verb, new_hr, secret, blobs, flags);
if (r < 0)
return r;
return 0;
}
-int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
+int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
HomeState state;
int r;
@@ -1743,7 +1803,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
if (r < 0)
return r;
- r = home_update_internal(h, "update", hr, NULL, error);
+ r = home_update_internal(h, "update", hr, NULL, blobs, flags, error);
if (r < 0)
return r;
@@ -1830,7 +1890,7 @@ int home_resize(Home *h,
c = TAKE_PTR(signed_c);
}
- r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, error);
+ r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, NULL, 0, error);
if (r < 0)
return r;
@@ -1946,7 +2006,7 @@ int home_passwd(Home *h,
return r;
}
- r = home_update_internal(h, "passwd", signed_c, merged_secret, error);
+ r = home_update_internal(h, "passwd", signed_c, merged_secret, NULL, 0, error);
if (r < 0)
return r;
@@ -2003,7 +2063,7 @@ int home_lock(Home *h, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
- r = home_start_work(h, "lock", h->record, NULL);
+ r = home_start_work(h, "lock", h->record, NULL, NULL, 0);
if (r < 0)
return r;
@@ -2018,7 +2078,7 @@ static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state
assert(secret);
assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
- r = home_start_work(h, "unlock", h->record, secret);
+ r = home_start_work(h, "unlock", h->record, secret, NULL, 0);
if (r < 0)
return r;
diff --git a/src/home/homed-home.h b/src/home/homed-home.h
index 6c069ab5f0..0f3d1ce325 100644
--- a/src/home/homed-home.h
+++ b/src/home/homed-home.h
@@ -3,6 +3,7 @@
typedef struct Home Home;
+#include "hashmap.h"
#include "homed-manager.h"
#include "homed-operation.h"
#include "list.h"
@@ -193,9 +194,9 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error);
int home_activate(Home *h, bool if_referenced, UserRecord *secret, sd_bus_error *error);
int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error);
int home_deactivate(Home *h, bool force, sd_bus_error *error);
-int home_create(Home *h, UserRecord *secret, sd_bus_error *error);
+int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
int home_remove(Home *h, sd_bus_error *error);
-int home_update(Home *h, UserRecord *new_record, sd_bus_error *error);
+int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error);
int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error);
int home_unregister(Home *h, sd_bus_error *error);
diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c
index c8e232f425..1e63012a06 100644
--- a/src/home/homed-manager-bus.c
+++ b/src/home/homed-manager-bus.c
@@ -340,7 +340,7 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu
return generic_home_method(userdata, message, bus_home_method_deactivate, error);
}
-static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) {
+static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
bool signed_locally;
Home *other;
@@ -370,6 +370,15 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd
if (r != -ESRCH)
return r;
+ if (blobs) {
+ const char *failed = NULL;
+ r = user_record_ensure_blob_manifest(hr, blobs, &failed);
+ if (r == -EINVAL)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
+ }
+
r = manager_verify_user_record(m, hr);
switch (r) {
@@ -458,7 +467,7 @@ static int method_register_home(
if (r == 0)
return 1; /* Will call us back */
- r = validate_and_allocate_home(m, hr, &h, error);
+ r = validate_and_allocate_home(m, hr, NULL, &h, error);
if (r < 0)
return r;
@@ -475,12 +484,11 @@ static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bu
return generic_home_method(userdata, message, bus_home_method_unregister, error);
}
-static int method_create_home(
- sd_bus_message *message,
- void *userdata,
- sd_bus_error *error) {
+static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+ uint64_t flags = 0;
Manager *m = ASSERT_PTR(userdata);
Home *h;
int r;
@@ -491,6 +499,18 @@ static int method_create_home(
if (r < 0)
return r;
+ if (endswith(sd_bus_message_get_member(message), "Ex")) {
+ r = bus_message_read_blobs(message, &blobs, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "t", &flags);
+ if (r < 0)
+ return r;
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+ }
+
r = bus_verify_polkit_async(
message,
"org.freedesktop.home1.create-home",
@@ -502,11 +522,11 @@ static int method_create_home(
if (r == 0)
return 1; /* Will call us back */
- r = validate_and_allocate_home(m, hr, &h, error);
+ r = validate_and_allocate_home(m, hr, blobs, &h, error);
if (r < 0)
return r;
- r = home_create(h, hr, error);
+ r = home_create(h, hr, blobs, flags, error);
if (r < 0)
goto fail;
@@ -544,6 +564,8 @@ static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_
static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+ uint64_t flags = 0;
Manager *m = ASSERT_PTR(userdata);
Home *h;
int r;
@@ -554,13 +576,23 @@ static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_er
if (r < 0)
return r;
+ if (endswith(sd_bus_message_get_member(message), "Ex")) {
+ r = bus_message_read_blobs(message, &blobs, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "t", &flags);
+ if (r < 0)
+ return r;
+ }
+
assert(hr->user_name);
h = hashmap_get(m->homes_by_name, hr->user_name);
if (!h)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name);
- return bus_home_method_update_record(h, message, hr, error);
+ return bus_home_method_update_record(h, message, hr, blobs, flags, error);
}
static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -769,6 +801,11 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_NO_RESULT,
method_create_home,
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+ SD_BUS_METHOD_WITH_ARGS("CreateHomeEx",
+ SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+ SD_BUS_NO_RESULT,
+ method_create_home,
+ SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
/* Create $HOME for already registered JSON entry */
SD_BUS_METHOD_WITH_ARGS("RealizeHome",
@@ -804,6 +841,11 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_NO_RESULT,
method_update_home,
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+ SD_BUS_METHOD_WITH_ARGS("UpdateHomeEx",
+ SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+ SD_BUS_NO_RESULT,
+ method_update_home,
+ SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
SD_BUS_METHOD_WITH_ARGS("ResizeHome",
SD_BUS_ARGS("s", user_name, "t", size, "s", secret),
diff --git a/src/home/homework-blob.c b/src/home/homework-blob.c
index b95a0dc709..17cb7d6ce3 100644
--- a/src/home/homework-blob.c
+++ b/src/home/homework-blob.c
@@ -241,3 +241,61 @@ int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled) {
}
return 0;
}
+
+int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs) {
+ _cleanup_free_ char *fn = NULL;
+ _cleanup_close_ int base_dfd = -EBADF, dfd = -EBADF;
+ uint64_t total_size = 0;
+ const char *filename;
+ const void *v;
+ int r;
+
+ assert(h);
+
+ if (!blobs) /* Shortcut: If no blobs are passed from dbus, we have nothing to do. */
+ return 0;
+
+ base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (base_dfd < 0)
+ return log_error_errno(errno, "Failed to open system blob base dir: %m");
+
+ if (hashmap_isempty(blobs)) {
+ /* Shortcut: If blobs was passed but empty, we can simply delete the contents
+ * of the directory. */
+ r = rm_rf_at(base_dfd, h->user_name, REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to empty out system blob dir: %m");
+ return 0;
+ }
+
+ r = tempfn_random(h->user_name, NULL, &fn);
+ if (r < 0)
+ return r;
+
+ dfd = open_mkdir_at(base_dfd, fn, O_EXCL|O_CLOEXEC, 0755);
+ if (dfd < 0)
+ return log_error_errno(errno, "Failed to create system blob dir: %m");
+
+ HASHMAP_FOREACH_KEY(v, filename, blobs) {
+ r = copy_one_blob(PTR_TO_FD(v), dfd, filename, &total_size, 0, h->blob_manifest);
+ if (r == -EFBIG)
+ break;
+ if (r < 0) {
+ log_error_errno(r, "Failed to copy %s into system blob dir: %m", filename);
+ goto fail;
+ }
+ }
+
+ r = install_file(base_dfd, fn, base_dfd, h->user_name, INSTALL_REPLACE);
+ if (r < 0) {
+ log_error_errno(r, "Failed to move system blob dir into place: %m");
+ goto fail;
+ }
+
+ log_info("Replaced system blob directory.");
+ return 0;
+
+fail:
+ (void) rm_rf_at(base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+ return r;
+}
diff --git a/src/home/homework-blob.h b/src/home/homework-blob.h
index 7bf4f514b9..fbe6c82cd4 100644
--- a/src/home/homework-blob.h
+++ b/src/home/homework-blob.h
@@ -5,3 +5,5 @@
#include "user-record.h"
int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled);
+
+int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs);
diff --git a/src/home/homework.c b/src/home/homework.c
index 4575471041..39e0051486 100644
--- a/src/home/homework.c
+++ b/src/home/homework.c
@@ -25,6 +25,7 @@
#include "memory-util.h"
#include "missing_magic.h"
#include "mount-util.h"
+#include "parse-util.h"
#include "path-util.h"
#include "recovery-key.h"
#include "rm-rf.h"
@@ -1314,7 +1315,7 @@ static int determine_default_storage(UserStorage *ret) {
return 0;
}
-static int home_create(UserRecord *h, UserRecord **ret_home) {
+static int home_create(UserRecord *h, Hashmap *blobs, UserRecord **ret_home) {
_cleanup_strv_free_erase_ char **effective_passwords = NULL;
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
@@ -1376,6 +1377,10 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(r, USER_TEST_ABSENT, USER_TEST_UNDEFINED, USER_TEST_MAYBE))
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image path %s already exists, refusing.", user_record_image_path(h));
+ r = home_apply_new_blob_dir(h, blobs);
+ if (r < 0)
+ return r;
+
switch (user_record_storage(h)) {
case USER_LUKS:
@@ -1581,7 +1586,7 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags
return has_mount; /* return true if the home record is already active */
}
-static int home_update(UserRecord *h, UserRecord **ret) {
+static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
@@ -1600,6 +1605,10 @@ static int home_update(UserRecord *h, UserRecord **ret) {
if (r < 0)
return r;
+ r = home_apply_new_blob_dir(h, blobs);
+ if (r < 0)
+ return r;
+
r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;
@@ -1867,10 +1876,12 @@ static int run(int argc, char *argv[]) {
_cleanup_(user_record_unrefp) UserRecord *home = NULL, *new_home = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_fclose_ FILE *opened_file = NULL;
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
unsigned line = 0, column = 0;
- const char *json_path = NULL;
+ const char *json_path = NULL, *blob_filename;
FILE *json_file;
usec_t start;
+ JsonVariant *fdmap, *blob_fd_variant;
int r;
start = now(CLOCK_MONOTONIC);
@@ -1901,6 +1912,48 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
+ fdmap = json_variant_by_key(v, HOMEWORK_BLOB_FDMAP_FIELD);
+ if (fdmap) {
+ r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ JSON_VARIANT_OBJECT_FOREACH(blob_filename, blob_fd_variant, fdmap) {
+ _cleanup_free_ char *filename = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(json_variant_is_integer(blob_fd_variant));
+ assert(json_variant_integer(blob_fd_variant) >= 0);
+ assert(json_variant_integer(blob_fd_variant) <= INT_MAX - SD_LISTEN_FDS_START);
+ fd = SD_LISTEN_FDS_START + (int) json_variant_integer(blob_fd_variant);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *resolved = NULL;
+ r = fd_get_path(fd, &resolved);
+ log_debug("Got blob from daemon: %s (%d) → %s",
+ blob_filename, fd, resolved ?: STRERROR(r));
+ }
+
+ filename = strdup(blob_filename);
+ if (!filename)
+ return log_oom();
+
+ r = fd_cloexec(fd, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable O_CLOEXEC on blob %s: %m", filename);
+
+ r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
+ if (r < 0)
+ return log_error_errno(r, "Failed to insert blob %s into map: %m", filename);
+ TAKE_PTR(filename); /* Ownership transfers to hashmap */
+ TAKE_FD(fd);
+ }
+
+ r = json_variant_filter(&v, STRV_MAKE(HOMEWORK_BLOB_FDMAP_FIELD));
+ if (r < 0)
+ return log_error_errno(r, "Failed to strip internal fdmap from JSON: %m");
+ }
+
home = user_record_new();
if (!home)
return log_oom();
@@ -1944,11 +1997,11 @@ static int run(int argc, char *argv[]) {
else if (streq(argv[1], "deactivate-force"))
r = home_deactivate(home, true);
else if (streq(argv[1], "create"))
- r = home_create(home, &new_home);
+ r = home_create(home, blobs, &new_home);
else if (streq(argv[1], "remove"))
r = home_remove(home);
else if (streq(argv[1], "update"))
- r = home_update(home, &new_home);
+ r = home_update(home, blobs, &new_home);
else if (streq(argv[1], "resize")) /* Resize on user request */
r = home_resize(home, false, &new_home);
else if (streq(argv[1], "resize-auto")) /* Automatic resize */
diff --git a/src/home/org.freedesktop.home1.conf b/src/home/org.freedesktop.home1.conf
index d2c4b9dd29..9ac34aea55 100644
--- a/src/home/org.freedesktop.home1.conf
+++ b/src/home/org.freedesktop.home1.conf
@@ -79,6 +79,10 @@
<allow send_destination="org.freedesktop.home1"
send_interface="org.freedesktop.home1.Manager"
+ send_member="CreateHomeEx"/>
+
+ <allow send_destination="org.freedesktop.home1"
+ send_interface="org.freedesktop.home1.Manager"
send_member="RealizeHome"/>
<allow send_destination="org.freedesktop.home1"
@@ -99,6 +103,10 @@
<allow send_destination="org.freedesktop.home1"
send_interface="org.freedesktop.home1.Manager"
+ send_member="UpdateHomeEx"/>
+
+ <allow send_destination="org.freedesktop.home1"
+ send_interface="org.freedesktop.home1.Manager"
send_member="ResizeHome"/>
<allow send_destination="org.freedesktop.home1"
@@ -185,6 +193,10 @@
<allow send_destination="org.freedesktop.home1"
send_interface="org.freedesktop.home1.Home"
+ send_member="UpdateEx"/>
+
+ <allow send_destination="org.freedesktop.home1"
+ send_interface="org.freedesktop.home1.Home"
send_member="Resize"/>
<allow send_destination="org.freedesktop.home1"
diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c
index b18a77d740..3ae0883cb0 100644
--- a/src/home/user-record-util.c
+++ b/src/home/user-record-util.c
@@ -3,6 +3,7 @@
#include <sys/xattr.h>
#include "errno-util.h"
+#include "fd-util.h"
#include "home-util.h"
#include "id128-util.h"
#include "libcrypt-util.h"
@@ -10,6 +11,7 @@
#include "recovery-key.h"
#include "mountpoint-util.h"
#include "path-util.h"
+#include "sha256.h"
#include "stat-util.h"
#include "user-record-util.h"
#include "user-util.h"
@@ -1396,6 +1398,9 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage custom blob directories.");
}
+ if (json_variant_by_key(hr->json, HOMEWORK_BLOB_FDMAP_FIELD))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "User record contains unsafe internal fields.");
+
return 0;
}
@@ -1524,3 +1529,103 @@ int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight) {
h->mask |= USER_RECORD_PER_MACHINE;
return 0;
}
+
+int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_hashmap_free_ Hashmap *manifest = NULL;
+ const char *filename;
+ void *key, *value;
+ uint64_t total_size = 0;
+ int r;
+
+ assert(h);
+ assert(h->json);
+ assert(blobs);
+ assert(ret_failed);
+
+ /* Ensures that blobManifest exists (possibly creating it using the
+ * contents of blobs), and that the set of keys in both hashmaps are
+ * exactly the same. If it fails to handle one blob file, the filename
+ * is put it ret_failed for nicer error reporting. ret_failed is a pointer
+ * to the same memory blobs uses to store its keys, so it is valid for
+ * as long as blobs is valid and the corresponding key isn't removed! */
+
+ if (h->blob_manifest) {
+ /* blobManifest already exists. In this case we verify
+ * that the sets of keys are equal and that's it */
+
+ HASHMAP_FOREACH_KEY(value, key, h->blob_manifest)
+ if (!hashmap_contains(blobs, key))
+ return -EINVAL;
+ HASHMAP_FOREACH_KEY(value, key, blobs)
+ if (!hashmap_contains(h->blob_manifest, key))
+ return -EINVAL;
+
+ return 0;
+ }
+
+ /* blobManifest doesn't exist, so we need to create it */
+
+ HASHMAP_FOREACH_KEY(value, filename, blobs) {
+ _cleanup_free_ char *filename_dup = NULL;
+ _cleanup_free_ uint8_t *hash = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *hash_json = NULL;
+ int fd = PTR_TO_FD(value);
+ off_t initial, size;
+
+ *ret_failed = filename;
+
+ filename_dup = strdup(filename);
+ if (!filename_dup)
+ return -ENOMEM;
+
+ hash = malloc(SHA256_DIGEST_SIZE);
+ if (!hash)
+ return -ENOMEM;
+
+ initial = lseek(fd, 0, SEEK_CUR);
+ if (initial < 0)
+ return -errno;
+
+ r = sha256_fd(fd, BLOB_DIR_MAX_SIZE, hash);
+ if (r < 0)
+ return r;
+
+ size = lseek(fd, 0, SEEK_CUR);
+ if (size < 0)
+ return -errno;
+ if (!DEC_SAFE(&size, initial))
+ return -EOVERFLOW;
+
+ if (!INC_SAFE(&total_size, size))
+ total_size = UINT64_MAX;
+ if (total_size > BLOB_DIR_MAX_SIZE)
+ return -EFBIG;
+
+ if (lseek(fd, initial, SEEK_SET) < 0)
+ return -errno;
+
+ r = json_variant_new_hex(&hash_json, hash, SHA256_DIGEST_SIZE);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename_dup, hash);
+ if (r < 0)
+ return r;
+ TAKE_PTR(filename_dup); /* Ownership transfers to hashmap */
+ TAKE_PTR(hash);
+
+ r = json_variant_set_field(&v, filename, hash_json);
+ if (r < 0)
+ return r;
+
+ *ret_failed = NULL;
+ }
+
+ r = json_variant_set_field_non_null(&h->json, "blobManifest", v);
+ if (r < 0)
+ return r;
+
+ h->blob_manifest = TAKE_PTR(manifest);
+ return 0;
+}
diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h
index 508e2bdb8d..1295a8e09a 100644
--- a/src/home/user-record-util.h
+++ b/src/home/user-record-util.h
@@ -6,6 +6,11 @@
#include "user-record.h"
#include "group-record.h"
+/* We intentionally use snake_case instead of the usual camelCase here to further
+ * reduce the chance of collision with a field any legitimate user record may ever
+ * want to set. */
+#define HOMEWORK_BLOB_FDMAP_FIELD "__systemd_homework_internal_blob_fdmap"
+
int user_record_synthesize(UserRecord *h, const char *user_name, const char *realm, const char *image_path, UserStorage storage, uid_t uid, gid_t gid);
int group_record_synthesize(GroupRecord *g, UserRecord *u);
@@ -63,3 +68,5 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error);
bool user_record_shall_rebalance(UserRecord *h);
int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight);
+
+int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed);