]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
xattr-util: rework getxattr_at_malloc()
authorLennart Poettering <lennart@poettering.net>
Wed, 12 Mar 2025 07:36:45 +0000 (08:36 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 18 Mar 2025 06:17:10 +0000 (07:17 +0100)
Let's return the size in a return parameter instead of the return value.
And if NULL is specified this tells us the caller doesn't care about the
size and expects a NUL terminated string. In that case look for an
embedded NUL byte, and refuse in that case.

This should lock things down a bit, as we'll systematically refuse
embedded NUL strings now when we expect strings.

15 files changed:
src/basic/cgroup-util.c
src/basic/cgroup-util.h
src/basic/xattr-util.c
src/basic/xattr-util.h
src/core/cgroup.c
src/home/homework-fscrypt.c
src/journal/journald-client.c
src/oom/oomd-util.c
src/oom/test-oomd-util.c
src/shared/cgroup-show.c
src/shared/copy.c
src/shared/dissect-image.c
src/shared/smack-util.c
src/test/test-copy.c
src/test/test-xattr-util.c

index 3c3dfdbfaba4dc60647a3e338fc6dc3fcad37101..12ae60a1864df80373de79aab0b8e9752ae66fdf 100644 (file)
@@ -723,7 +723,7 @@ int cg_get_xattr(const char *path, const char *name, void *value, size_t size) {
         return (int) n;
 }
 
-int cg_get_xattr_malloc(const char *path, const char *name, char **ret) {
+int cg_get_xattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size) {
         _cleanup_free_ char *fs = NULL;
         int r;
 
@@ -734,7 +734,7 @@ int cg_get_xattr_malloc(const char *path, const char *name, char **ret) {
         if (r < 0)
                 return r;
 
-        return lgetxattr_malloc(fs, name, ret);
+        return lgetxattr_malloc(fs, name, ret, ret_size);
 }
 
 int cg_get_xattr_bool(const char *path, const char *name) {
index 3e840847942ae13d1aad39c486363223d819c255..66dfe87f340de5abb760b2746e78f8c6f5628f18 100644 (file)
@@ -264,7 +264,7 @@ int cg_get_owner(const char *path, uid_t *ret_uid);
 
 int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags);
 int cg_get_xattr(const char *path, const char *name, void *value, size_t size);
-int cg_get_xattr_malloc(const char *path, const char *name, char **ret);
+int cg_get_xattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size);
 /* Returns negative on error, and 0 or 1 on success for the bool value */
 int cg_get_xattr_bool(const char *path, const char *name);
 int cg_remove_xattr(const char *path, const char *name);
index 7ae94761f8302e35d9403db3e3092e51c118f3ca..4e69693e064afa317b1f791a63528004e23389a3 100644 (file)
@@ -59,7 +59,6 @@ static int normalize_and_maybe_pin_inode(
                 if (r < 0)
                         return r;
                 *ret_opath = r;
-
                 *ret_tfd = -EBADF;
                 return 0;
         }
@@ -77,7 +76,7 @@ static int normalize_and_maybe_pin_inode(
         return 0;
 }
 
-static int getxattr_pinned_internal(
+static ssize_t getxattr_pinned_internal(
                 int fd,
                 const char *path,
                 int at_flags,
@@ -104,11 +103,7 @@ static int getxattr_pinned_internal(
                 return -errno;
 
         assert((size_t) n <= size);
-
-        if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
-                return -E2BIG;
-
-        return (int) n;
+        return n;
 }
 
 int getxattr_at_malloc(
@@ -116,7 +111,8 @@ int getxattr_at_malloc(
                 const char *path,
                 const char *name,
                 int at_flags,
-                char **ret) {
+                char **ret,
+                size_t *ret_size) {
 
         _cleanup_close_ int opened_fd = -EBADF;
         bool by_procfs;
@@ -154,20 +150,31 @@ int getxattr_at_malloc(
 
                 l = MALLOC_ELEMENTSOF(v) - 1;
 
-                r = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, v, l);
-                if (r >= 0) {
-                        v[r] = 0; /* NUL terminate */
+                ssize_t n;
+                n = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, v, l);
+                if (n >= 0) {
+                        /* Refuse extended attributes with embedded NUL bytes if the caller isn't interested
+                         * in the size. After all this must mean the caller assumes we return a NUL
+                         * terminated strings, but if there's a NUL byte embedded they are definitely not
+                         * regular strings */
+                        if (!ret_size && n > 1 && memchr(v, 0, n - 1))
+                                return -EBADMSG;
+
+                        v[n] = 0; /* NUL terminate */
                         *ret = TAKE_PTR(v);
-                        return r;
+                        if (ret_size)
+                                *ret_size = (size_t) n;
+
+                        return 0;
                 }
-                if (r != -ERANGE)
-                        return r;
+                if (n != -ERANGE)
+                        return (int) n;
 
-                r = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, NULL, 0);
-                if (r < 0)
-                        return r;
+                n = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, NULL, 0);
+                if (n < 0)
+                        return (int) n;
 
-                l = (size_t) r;
+                l = (size_t) n;
         }
 }
 
@@ -175,13 +182,10 @@ int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags) {
         _cleanup_free_ char *v = NULL;
         int r;
 
-        r = getxattr_at_malloc(fd, path, name, at_flags, &v);
+        r = getxattr_at_malloc(fd, path, name, at_flags, &v, /* ret_size= */ NULL);
         if (r < 0)
                 return r;
 
-        if (memchr(v, 0, r)) /* Refuse embedded NUL byte */
-                return -EINVAL;
-
         return parse_boolean(v);
 }
 
@@ -408,9 +412,10 @@ int getcrtime_at(
         else
                 a = USEC_INFINITY;
 
-        r = getxattr_at_malloc(fd, path, "user.crtime_usec", at_flags, (char**) &le);
+        size_t le_size;
+        r = getxattr_at_malloc(fd, path, "user.crtime_usec", at_flags, (char**) &le, &le_size);
         if (r >= 0) {
-                if (r != sizeof(*le))
+                if (le_size != sizeof(*le))
                         r = -EIO;
                 else
                         r = parse_crtime(*le, &b);
index 03e6a46cdbc712a168e75b5113dab0bad67e5f9a..8882ebf7749dd99c7c558d4ade510e56f7a33cfd 100644 (file)
@@ -1,21 +1,22 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <sys/types.h>
 
 #include "time-util.h"
 
-int getxattr_at_malloc(int fd, const char *path, const char *name, int at_flags, char **ret);
-static inline int getxattr_malloc(const char *path, const char *name, char **ret) {
-        return getxattr_at_malloc(AT_FDCWD, path, name, AT_SYMLINK_FOLLOW, ret);
+int getxattr_at_malloc(int fd, const char *path, const char *name, int at_flags, char **ret, size_t *ret_size);
+static inline int getxattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size) {
+        return getxattr_at_malloc(AT_FDCWD, path, name, AT_SYMLINK_FOLLOW, ret, ret_size);
 }
-static inline int lgetxattr_malloc(const char *path, const char *name, char **ret) {
-        return getxattr_at_malloc(AT_FDCWD, path, name, 0, ret);
+static inline int lgetxattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size) {
+        return getxattr_at_malloc(AT_FDCWD, path, name, 0, ret, ret_size);
 }
-static inline int fgetxattr_malloc(int fd, const char *name, char **ret) {
-        return getxattr_at_malloc(fd, NULL, name, AT_EMPTY_PATH, ret);
+static inline int fgetxattr_malloc(int fd, const char *name, char **ret, size_t *ret_size) {
+        return getxattr_at_malloc(fd, NULL, name, AT_EMPTY_PATH, ret, ret_size);
 }
 
 int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags);
index 8e197a0303f91225b2c6ea7463e58072b790e94c..2c1bafa29afec30a8634d09d9db73dd8b7b26f67 100644 (file)
@@ -3968,10 +3968,10 @@ int unit_check_oomd_kill(Unit *u) {
         r = cg_all_unified();
         if (r < 0)
                 return log_unit_debug_errno(u, r, "Couldn't determine whether we are in all unified mode: %m");
-        else if (r == 0)
+        if (r == 0)
                 return 0;
 
-        r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_ooms", &value);
+        r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_ooms", &value, /* ret_size= */ NULL);
         if (r < 0 && !ERRNO_IS_XATTR_ABSENT(r))
                 return r;
 
@@ -3989,7 +3989,7 @@ int unit_check_oomd_kill(Unit *u) {
 
         n = 0;
         value = mfree(value);
-        r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_kill", &value);
+        r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_kill", &value, /* ret_size= */ NULL);
         if (r >= 0 && !isempty(value))
                 (void) safe_atou64(value, &n);
 
index 03ad3da1b26f5eb5c7502f01005ed068ee4b59b2..e8864051c204995cd4135bd5c4050064429a4824 100644 (file)
@@ -309,9 +309,8 @@ static int fscrypt_setup(
         NULSTR_FOREACH(xa, xattr_buf) {
                 _cleanup_free_ void *salt = NULL, *encrypted = NULL;
                 _cleanup_free_ char *value = NULL;
-                size_t salt_size, encrypted_size;
+                size_t salt_size, encrypted_size, vsize;
                 const char *nr, *e;
-                int n;
 
                 /* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32-bit unsigned integer */
                 nr = startswith(xa, "trusted.fscrypt_slot");
@@ -320,13 +319,13 @@ static int fscrypt_setup(
                 if (safe_atou32(nr, NULL) < 0)
                         continue;
 
-                n = fgetxattr_malloc(setup->root_fd, xa, &value);
-                if (n == -ENODATA) /* deleted by now? */
+                r = fgetxattr_malloc(setup->root_fd, xa, &value, &vsize);
+                if (r == -ENODATA) /* deleted by now? */
                         continue;
-                if (n < 0)
-                        return log_error_errno(n, "Failed to read %s xattr: %m", xa);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read %s xattr: %m", xa);
 
-                e = memchr(value, ':', n);
+                e = memchr(value, ':', vsize);
                 if (!e)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator.", xa);
 
@@ -334,7 +333,7 @@ static int fscrypt_setup(
                 if (r < 0)
                         return log_error_errno(r, "Failed to decode salt of %s: %m", xa);
 
-                r = unbase64mem_full(e + 1, n - (e - value) - 1, /* secure = */ false, &encrypted, &encrypted_size);
+                r = unbase64mem_full(e + 1, vsize - (e - value) - 1, /* secure = */ false, &encrypted, &encrypted_size);
                 if (r < 0)
                         return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
 
index 07b903e2b91b21812b67b00b6eb472ed54b07a2c..fdba70c3d5022f9ff5ec97a2d108ddc22e3e6b99 100644 (file)
@@ -46,25 +46,26 @@ static int client_parse_log_filter_nulstr(const char *nulstr, size_t len, Set **
 }
 
 int client_context_read_log_filter_patterns(ClientContext *c, const char *cgroup) {
-        char *deny_list_xattr, *xattr_end;
-        _cleanup_free_ char *xattr = NULL, *unit_cgroup = NULL;
-        _cleanup_set_free_ Set *allow_list = NULL, *deny_list = NULL;
         int r;
 
         assert(c);
 
+        _cleanup_free_ char *unit_cgroup = NULL;
         r = cg_path_get_unit_path(cgroup, &unit_cgroup);
         if (r < 0)
                 return log_debug_errno(r, "Failed to get the unit's cgroup path for %s: %m", cgroup);
 
-        r = cg_get_xattr_malloc(unit_cgroup, "user.journald_log_filter_patterns", &xattr);
+        _cleanup_free_ char *xattr = NULL;
+        size_t xattr_size = 0;
+        r = cg_get_xattr_malloc(unit_cgroup, "user.journald_log_filter_patterns", &xattr, &xattr_size);
         if (ERRNO_IS_NEG_XATTR_ABSENT(r)) {
-                client_set_filtering_patterns(c, NULL, NULL);
+                client_set_filtering_patterns(c, /* allow_list= */ NULL, /* deny_list= */ NULL);
                 return 0;
-        } else if (r < 0)
+        }
+        if (r < 0)
                 return log_debug_errno(r, "Failed to get user.journald_log_filter_patterns xattr for %s: %m", unit_cgroup);
 
-        xattr_end = xattr + r;
+        const char *xattr_end = xattr + xattr_size;
 
         /* We expect '0xff' to be present in the attribute, even if the lists are empty. We expect the
          * following:
@@ -76,17 +77,19 @@ int client_context_read_log_filter_patterns(ClientContext *c, const char *cgroup
          *
          * We do not expect both the allow list and deny list to be empty, as this condition is tested
          * before writing to xattr. */
-        deny_list_xattr = memchr(xattr, (char)0xff, r);
+        const char *deny_list_xattr = memchr(xattr, (char)0xff, xattr_size);
         if (!deny_list_xattr)
                 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
                                        "Missing delimiter in cgroup user.journald_log_filter_patterns attribute: %m");
 
+        _cleanup_set_free_ Set *allow_list = NULL;
         r = client_parse_log_filter_nulstr(xattr, deny_list_xattr - xattr, &allow_list);
         if (r < 0)
                 return r;
 
         /* Use 'deny_list_xattr + 1' to skip '0xff'. */
         ++deny_list_xattr;
+        _cleanup_set_free_ Set *deny_list = NULL;
         r = client_parse_log_filter_nulstr(deny_list_xattr, xattr_end - deny_list_xattr, &deny_list);
         if (r < 0)
                 return r;
index b9967870390852ec9b6b8e391af9a266f1e7577e..e1076a64806dcf1af19d758a71e82534ad63c991 100644 (file)
@@ -40,7 +40,7 @@ static int increment_oomd_xattr(const char *path, const char *xattr, uint64_t nu
         assert(path);
         assert(xattr);
 
-        r = cg_get_xattr_malloc(path, xattr, &value);
+        r = cg_get_xattr_malloc(path, xattr, &value, /* ret_size= */ NULL);
         if (r < 0 && !ERRNO_IS_XATTR_ABSENT(r))
                 return r;
 
index d124132e0b0b60fe85fd8b563254e71f0219c23c..2ad20b2d15faf0bd9722b01e63e5fc7c0ccf89eb 100644 (file)
@@ -77,7 +77,7 @@ static void test_oomd_cgroup_kill(void) {
                         abort();
                 }
 
-                assert_se(cg_get_xattr_malloc(cgroup, "user.oomd_ooms", &v) >= 0);
+                ASSERT_OK(cg_get_xattr_malloc(cgroup, "user.oomd_ooms", &v, /* ret_size= */ NULL));
                 assert_se(streq(v, i == 0 ? "1" : "2"));
                 v = mfree(v);
 
@@ -85,7 +85,7 @@ static void test_oomd_cgroup_kill(void) {
                 sleep(2);
                 assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, cgroup) == true);
 
-                assert_se(cg_get_xattr_malloc(cgroup, "user.oomd_kill", &v) >= 0);
+                ASSERT_OK(cg_get_xattr_malloc(cgroup, "user.oomd_kill", &v, /* ret_size= */ NULL));
                 assert_se(streq(v, i == 0 ? "2" : "4"));
         }
 }
index 21d72ea0dcfdbb29eca0a9eae19b3b163777331d..35bcac0f61831c0c2882a2701e9f9aada1965dda 100644 (file)
@@ -186,13 +186,13 @@ static int show_cgroup_name(
 
                 NULSTR_FOREACH(xa, nl) {
                         _cleanup_free_ char *x = NULL, *y = NULL, *buf = NULL;
-                        int n;
 
                         if (!STARTSWITH_SET(xa, "user.", "trusted."))
                                 continue;
 
-                        n = fgetxattr_malloc(fd, xa, &buf);
-                        if (n < 0) {
+                        size_t buf_size;
+                        r = fgetxattr_malloc(fd, xa, &buf, &buf_size);
+                        if (r < 0) {
                                 log_debug_errno(r, "Failed to read xattr '%s' off '%s', ignoring: %m", xa, path);
                                 continue;
                         }
@@ -201,7 +201,7 @@ static int show_cgroup_name(
                         if (!x)
                                 return -ENOMEM;
 
-                        y = cescape_length(buf, n);
+                        y = cescape_length(buf, buf_size);
                         if (!y)
                                 return -ENOMEM;
 
index 99f8b5eee0fe1235a3b44aef50f54de7541921c9..599e7f2fd17f7cbf61590e6ce451a6215d5c854b 100644 (file)
@@ -1670,18 +1670,18 @@ int copy_xattr(int df, const char *from, int dt, const char *to, CopyFlags copy_
                 return r;
 
         NULSTR_FOREACH(p, names) {
-                _cleanup_free_ char *value = NULL;
-
                 if (!FLAGS_SET(copy_flags, COPY_ALL_XATTRS) && !startswith(p, "user."))
                         continue;
 
-                r = getxattr_at_malloc(df, from, p, 0, &value);
+                _cleanup_free_ char *value = NULL;
+                size_t value_size;
+                r = getxattr_at_malloc(df, from, p, 0, &value, &value_size);
                 if (r == -ENODATA)
                         continue; /* gone by now */
                 if (r < 0)
                         return r;
 
-                RET_GATHER(ret, xsetxattr_full(dt, to, /* at_flags = */ 0, p, value, r, /* xattr_flags = */ 0));
+                RET_GATHER(ret, xsetxattr_full(dt, to, /* at_flags = */ 0, p, value, value_size, /* xattr_flags = */ 0));
         }
 
         return ret;
index 91f9d0c9ccfe2cf3a9b2e1140f78dcf781f7108e..8ce71423253f98d624d3fdce6cd693394a2fcc20 100644 (file)
@@ -3248,7 +3248,7 @@ int verity_settings_load(
                          * that doesn't exist for /usr */
 
                         if (designator < 0 || designator == PARTITION_ROOT) {
-                                r = getxattr_malloc(image, "user.verity.roothash", &text);
+                                r = getxattr_malloc(image, "user.verity.roothash", &text, /* ret_size= */ NULL);
                                 if (r < 0) {
                                         _cleanup_free_ char *p = NULL;
 
@@ -3277,7 +3277,7 @@ int verity_settings_load(
                                  * `usrhash`, because `usrroothash` or `rootusrhash` would just be too
                                  * confusing. We thus drop the reference to the root of the Merkle tree, and
                                  * just indicate which file system it's about. */
-                                r = getxattr_malloc(image, "user.verity.usrhash", &text);
+                                r = getxattr_malloc(image, "user.verity.usrhash", &text, /* ret_size= */ NULL);
                                 if (r < 0) {
                                         _cleanup_free_ char *p = NULL;
 
index 8537f3eda9953401b9613515b4ee31f9a8bab6eb..7eeca583adcb134df398a21a04afffbc89bc6b5e 100644 (file)
@@ -60,7 +60,7 @@ int mac_smack_read_at(int fd, const char *path, SmackAttr attr, char **ret) {
                 return 0;
         }
 
-        return getxattr_at_malloc(fd, path, smack_attr_to_string(attr), /* at_flags = */ 0, ret);
+        return getxattr_at_malloc(fd, path, smack_attr_to_string(attr), /* at_flags = */ 0, ret, /* ret_size= */ NULL);
 }
 
 int mac_smack_apply_at(int fd, const char *path, SmackAttr attr, const char *label) {
@@ -136,7 +136,7 @@ static int smack_fix_fd(
                 /* If the old label is identical to the new one, suppress any kind of error */
                 _cleanup_free_ char *old_label = NULL;
 
-                if (fgetxattr_malloc(fd, "security.SMACK64", &old_label) >= 0 &&
+                if (fgetxattr_malloc(fd, "security.SMACK64", &old_label, /* ret_size= */ NULL) >= 0 &&
                     streq(old_label, label))
                         return 0;
 
index 928afefe71392b613723731a763a48875cd205ff..ac98bcf2a18a250e835357390ef1a447d8103490 100644 (file)
@@ -204,7 +204,7 @@ TEST(copy_tree) {
                 assert_se(read_full_file(f, &buf, &sz) == 0);
                 ASSERT_STREQ(buf, "file\n");
 
-                k = lgetxattr_malloc(f, "user.testxattr", &c);
+                k = lgetxattr_malloc(f, "user.testxattr", &c, /* ret_size= */ NULL);
                 assert_se(xattr_worked < 0 || ((k >= 0) == !!xattr_worked));
 
                 if (k >= 0) {
index 169be82e596d8cdfdbd7a31b83268dd77ef6f789..880b932af6445bb7a02868b47d2869063bd742d2 100644 (file)
@@ -35,25 +35,34 @@ TEST(getxattr_at_malloc) {
                 return (void) log_tests_skipped_errno(errno, "no xattrs supported on /var/tmp");
         assert_se(r >= 0);
 
-        assert_se(getxattr_at_malloc(fd, "test", "user.foo", 0, &value) == 3);
+        ASSERT_OK(getxattr_at_malloc(fd, "test", "user.foo", 0, &value, /* ret_size= */ NULL));
         assert_se(memcmp(value, "bar", 3) == 0);
         value = mfree(value);
 
-        assert_se(getxattr_at_malloc(AT_FDCWD, x, "user.foo", 0, &value) == 3);
+        ASSERT_OK(getxattr_at_malloc(AT_FDCWD, x, "user.foo", 0, &value, /* ret_size= */ NULL));
         assert_se(memcmp(value, "bar", 3) == 0);
         value = mfree(value);
 
         safe_close(fd);
         fd = open("/", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY);
         assert_se(fd >= 0);
-        r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value);
+        r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value, /* ret_size= */ NULL);
         assert_se(ERRNO_IS_NEG_XATTR_ABSENT(r));
 
         safe_close(fd);
         fd = open(x, O_PATH|O_CLOEXEC);
         assert_se(fd >= 0);
-        assert_se(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value) == 3);
+        ASSERT_OK(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value, /* ret_size= */ NULL));
         ASSERT_STREQ(value, "bar");
+        value = mfree(value);
+
+        ASSERT_OK_ERRNO(setxattr(x, "user.foozu", "bar\0qux\0wal\0\0", 13, /* flags= */ 0));
+        ASSERT_ERROR(getxattr_at_malloc(fd, /* path= */ NULL, "user.foozu", 0, &value, /* ret_size= */ NULL), EBADMSG);
+        size_t value_size;
+        ASSERT_OK(getxattr_at_malloc(fd, /* path= */ NULL, "user.foozu", 0, &value, &value_size));
+        ASSERT_EQ(value_size, 13U);
+        ASSERT_EQ(memcmp(value, "bar\0qux\0wal\0\0", 13), 0);
+        ASSERT_EQ(value[13], 0); /* check extra NUL */
 }
 
 TEST(getcrtime) {
@@ -84,7 +93,13 @@ TEST(getcrtime) {
 static void verify_xattr(int dfd, const char *expected) {
         _cleanup_free_ char *value = NULL;
 
-        ASSERT_OK_EQ(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value), (int) strlen(expected));
+        ASSERT_OK(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value, /* ret_size= */ NULL));
+        ASSERT_STREQ(value, expected);
+        value = mfree(value);
+
+        size_t size;
+        ASSERT_OK(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value, &size));
+        ASSERT_EQ(size, strlen(expected));
         ASSERT_STREQ(value, expected);
 }
 
@@ -96,7 +111,12 @@ static void xattr_symlink_test_one(int fd, const char *path) {
         ASSERT_ERROR(xsetxattr_full(fd, path, 0, "trusted.bar", "bogus", SIZE_MAX, XATTR_CREATE), EEXIST);
 
         ASSERT_OK(xsetxattr(fd, path, 0, "trusted.test", "schaffen"));
-        ASSERT_OK_EQ(getxattr_at_malloc(fd, path, "trusted.test", 0, &value), (int) STRLEN("schaffen"));
+        ASSERT_OK(getxattr_at_malloc(fd, path, "trusted.test", 0, &value, /* ret_size= */ NULL));
+        ASSERT_STREQ(value, "schaffen");
+        value = mfree(value);
+        size_t size;
+        ASSERT_OK(getxattr_at_malloc(fd, path, "trusted.test", 0, &value, &size));
+        ASSERT_EQ(size, strlen(value));
         ASSERT_STREQ(value, "schaffen");
 
         r = listxattr_at_malloc(fd, path, 0, &list);
@@ -107,7 +127,7 @@ static void xattr_symlink_test_one(int fd, const char *path) {
         ASSERT_TRUE(strv_contains(list_split, "trusted.test"));
 
         ASSERT_OK(xremovexattr(fd, path, 0, "trusted.test"));
-        ASSERT_ERROR(getxattr_at_malloc(fd, path, "trusted.test", 0, &value), ENODATA);
+        ASSERT_ERROR(getxattr_at_malloc(fd, path, "trusted.test", 0, &value, /* ret_size= */ NULL), ENODATA);
 }
 
 TEST(xsetxattr) {