]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json-util: add a generic parser for parsing access mode/umask
authorLennart Poettering <lennart@poettering.net>
Thu, 20 Nov 2025 12:08:03 +0000 (13:08 +0100)
committerLennart Poettering <lennart@poettering.net>
Sun, 21 Dec 2025 06:04:42 +0000 (07:04 +0100)
Let's move this from user-record.c into generic code and let's beef it
up a bit: allow parsing octal strings. This is particular relevant given
json_variant_new_stat() generates the mode in that format, and we
probably should be able to parse our own fields (even though we
currently don't do that for the data from json_variant_new_stat()).

src/libsystemd/sd-json/json-util.c
src/libsystemd/sd-json/json-util.h
src/shared/user-record.c
src/test/test-json.c

index 6f42239b91ecec3a1558ee61f9cedcf27e0047e1..30dbfde37b6fa33d6d96dccabcb500f89cdd00e8 100644 (file)
@@ -701,3 +701,45 @@ int json_variant_new_fd_info(sd_json_variant **ret, int fd) {
                         JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("mountId", mntid),
                         SD_JSON_BUILD_PAIR_VARIANT("fileHandle", w));
 }
+
+int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+        mode_t *m = ASSERT_PTR(userdata);
+        int r;
+
+        if (sd_json_variant_is_null(variant)) {
+                *m = MODE_INVALID;
+                return 0;
+        }
+
+        /* Let the SD_JSON_STRICT determine if we allow suid/sgid/sticky or not */
+        mode_t limit = FLAGS_SET(flags, SD_JSON_STRICT) ? 0777 : 07777;
+
+        if (sd_json_variant_is_string(variant)) {
+                /* NB: we parse the mode in the usual octal if a string is specified. */
+
+                mode_t mode;
+                r = parse_mode(sd_json_variant_string(variant), &mode);
+                if (r < 0)
+                        return json_log(variant, flags, r, "JSON field '%s' is not a valid access mode string.", strna(name));
+
+                if (mode > limit)
+                        return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
+                                        "JSON field '%s' outside of valid range 0%s0%o.",
+                                        strna(name), glyph(GLYPH_ELLIPSIS), limit);
+
+                *m = mode;
+
+        } else if (sd_json_variant_is_unsigned(variant)) {
+
+                uint64_t k = sd_json_variant_unsigned(variant);
+                if (k > (uint64_t) limit)
+                        return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
+                                        "JSON field '%s' outside of valid range 0%s0%o.",
+                                        strna(name), glyph(GLYPH_ELLIPSIS), limit);
+
+                *m = (mode_t) k;
+        } else
+                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is neither a number nor a string.", strna(name));
+
+        return 0;
+}
index 753eb30f65e0b02ddfbe5dfa5ae9e5cfcb71c4a5..12c11a316a724be9c1644d31e49d0d277b9e0e0e 100644 (file)
@@ -127,6 +127,7 @@ int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dis
 int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 
 static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) {
         return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
index 6bb174b9cacd5e47399aad7e8f975ae238f0b5e5..e237f6e6ca2dbd7853fb018b7b43f9d4ec2b76eb 100644 (file)
@@ -462,50 +462,6 @@ static int json_dispatch_image_path(const char *name, sd_json_variant *variant,
         return 0;
 }
 
-static int json_dispatch_umask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
-        mode_t *m = userdata;
-        uint64_t k;
-
-        if (sd_json_variant_is_null(variant)) {
-                *m = MODE_INVALID;
-                return 0;
-        }
-
-        if (!sd_json_variant_is_unsigned(variant))
-                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
-
-        k = sd_json_variant_unsigned(variant);
-        if (k > 0777)
-                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
-                                "JSON field '%s' outside of valid range 0%s0777.",
-                                strna(name), glyph(GLYPH_ELLIPSIS));
-
-        *m = (mode_t) k;
-        return 0;
-}
-
-static int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
-        mode_t *m = userdata;
-        uint64_t k;
-
-        if (sd_json_variant_is_null(variant)) {
-                *m = MODE_INVALID;
-                return 0;
-        }
-
-        if (!sd_json_variant_is_unsigned(variant))
-                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
-
-        k = sd_json_variant_unsigned(variant);
-        if (k > 07777)
-                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
-                                "JSON field '%s' outside of valid range 0%s07777.",
-                                strna(name), glyph(GLYPH_ELLIPSIS));
-
-        *m = (mode_t) k;
-        return 0;
-}
-
 static int json_dispatch_locale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
         char **s = userdata;
         const char *n;
@@ -1273,7 +1229,7 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
                 { "iconName",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, icon_name),                     SD_JSON_STRICT },
                 { "location",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, location),                      0              },
                 { "shell",                      SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path,       offsetof(UserRecord, shell),                         0              },
-                { "umask",                      SD_JSON_VARIANT_UNSIGNED,      json_dispatch_umask,                  offsetof(UserRecord, umask),                         0              },
+                { "umask",                      _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,            offsetof(UserRecord, umask),                         SD_JSON_STRICT },
                 { "environment",                SD_JSON_VARIANT_ARRAY,         json_dispatch_strv_environment,       offsetof(UserRecord, environment),                   0              },
                 { "timeZone",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, time_zone),                     SD_JSON_STRICT },
                 { "preferredLanguage",          SD_JSON_VARIANT_STRING,        json_dispatch_locale,                 offsetof(UserRecord, preferred_language),            0              },
@@ -1287,7 +1243,7 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
                 { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size),                     0              },
                 { "diskSizeRelative",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size_relative),            0              },
                 { "skeletonDirectory",          SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, skeleton_directory),            SD_JSON_STRICT },
-                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
+                { "accessMode",                 _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
                 { "tasksMax",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, tasks_max),                     0              },
                 { "memoryHigh",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_high),                   0              },
                 { "memoryMax",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_max),                    0              },
@@ -1397,7 +1353,7 @@ static int dispatch_status(const char *name, sd_json_variant *variant, sd_json_d
                 { "rateLimitBeginUSec",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_begin_usec),          0              },
                 { "rateLimitCount",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_count),               0              },
                 { "removable",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,      offsetof(UserRecord, removable),                     0              },
-                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
+                { "accessMode",                 _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
                 { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
                 { "fallbackShell",              SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0              },
                 { "fallbackHomeDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0              },
@@ -1633,7 +1589,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
                 { "lastChangeUSec",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, last_change_usec),              0              },
                 { "lastPasswordChangeUSec",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, last_password_change_usec),     0              },
                 { "shell",                      SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path,       offsetof(UserRecord, shell),                         0              },
-                { "umask",                      SD_JSON_VARIANT_UNSIGNED,      json_dispatch_umask,                  offsetof(UserRecord, umask),                         0              },
+                { "umask",                      _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,            offsetof(UserRecord, umask),                         SD_JSON_STRICT },
                 { "environment",                SD_JSON_VARIANT_ARRAY,         json_dispatch_strv_environment,       offsetof(UserRecord, environment),                   0              },
                 { "timeZone",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, time_zone),                     SD_JSON_STRICT },
                 { "preferredLanguage",          SD_JSON_VARIANT_STRING,        json_dispatch_locale,                 offsetof(UserRecord, preferred_language),            0              },
@@ -1647,7 +1603,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
                 { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size),                     0              },
                 { "diskSizeRelative",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size_relative),            0              },
                 { "skeletonDirectory",          SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, skeleton_directory),            SD_JSON_STRICT },
-                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
+                { "accessMode",                 _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
                 { "tasksMax",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, tasks_max),                     0              },
                 { "memoryHigh",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_high),                   0              },
                 { "memoryMax",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_max),                    0              },
index 64a0de3fe94b309abd453560035fb660155e17f9..1f0e07d7d4da3d8b4217981c1383a7fe2d7ca6a0 100644 (file)
@@ -1477,4 +1477,50 @@ TEST(unit_name) {
                                   &data), EINVAL);
 }
 
+TEST(access_mode) {
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        ASSERT_OK(sd_json_parse("{"
+                                " \"a\" : \"0755\", "
+                                " \"b\" : 448, "
+                                " \"c\" : null, "
+                                " \"d\" : \"01755\" "
+                                "}",
+                                /* flags= */ 0,
+                                &v,
+                                /* reterr_line= */ NULL,
+                                /* reterr_column= */ NULL));
+
+        struct {
+                mode_t a, b, c, d;
+        } mm = { 1, 2, 3, 4 };
+
+        ASSERT_OK(sd_json_dispatch(
+                                  v,
+                                  (const sd_json_dispatch_field[]) {
+                                          { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode, voffsetof(mm, a), 0 },
+                                          { "b", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode, voffsetof(mm, b), 0 },
+                                          { "c", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode, voffsetof(mm, c), 0 },
+                                          { "d", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode, voffsetof(mm, d), 0 },
+                                          {},
+                                  },
+                                  /* flags= */ 0,
+                                  &mm));
+
+        ASSERT_EQ(mm.a, (mode_t) 0755);
+        ASSERT_EQ(mm.b, (mode_t) 0700);
+        ASSERT_EQ(mm.c, MODE_INVALID);
+        ASSERT_EQ(mm.d, (mode_t) 01755);
+
+        /* retry with SD_JSON_STRICT, where 'd' should not parse anymore */
+        ASSERT_ERROR(sd_json_dispatch(
+                                  v,
+                                  (const sd_json_dispatch_field[]) {
+                                          { "d", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode, voffsetof(mm, d), SD_JSON_STRICT },
+                                          {},
+                                  },
+                                  /* flags= */ SD_JSON_ALLOW_EXTENSIONS,
+                                  &mm), ERANGE);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);