From 7632fa1da2dd9d60b4a73d67d731876e9dd706f2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:08:17 +0200 Subject: [PATCH 1/8] iso9660: prefer uint8_t for 'arbitrary bytes' --- src/repart/iso9660.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h index 7a51928fd6..23db9e919a 100644 --- a/src/repart/iso9660.h +++ b/src/repart/iso9660.h @@ -127,14 +127,14 @@ struct _packed_ iso9660_primary_volume_descriptor { uint8_t file_structure_version; /* 1 */ uint8_t unused_5; char application_used[512]; - char reserved[653]; + uint8_t reserved[653]; }; assert_cc(sizeof(struct iso9660_primary_volume_descriptor) == 2048); struct _packed_ el_torito_validation_entry { uint8_t header_indicator; uint8_t platform; - char reserved[2]; + uint8_t reserved[2]; char id_string[24]; le16_t checksum; uint8_t key_bytes[2]; From b0d52a8101db84dca26e85bc6f81d4c24f3776dd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:10:16 +0200 Subject: [PATCH 2/8] iso9660: rename all functions so that they are prefixed by iso9660 --- src/repart/iso9660.c | 18 +++++++++--------- src/repart/iso9660.h | 12 ++++++------ src/repart/repart.c | 38 +++++++++++++++++++------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 5bc9588e68..3f200942b2 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -8,7 +8,7 @@ #include "string-util.h" #include "time-util.h" -void no_iso9660_datetime(struct iso9660_datetime *ret) { +void iso9660_datetime_zero(struct iso9660_datetime *ret) { assert(ret); memcpy(ret->year, "0000", 4); @@ -21,7 +21,7 @@ void no_iso9660_datetime(struct iso9660_datetime *ret) { ret->zone = 0; } -int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret) { +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) { struct tm t; int r; @@ -49,7 +49,7 @@ int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret return 0; } -int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret) { +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret) { struct tm t; int r; @@ -76,15 +76,15 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time return 0; } -static bool valid_iso9660_string(const char *str, bool allow_a_chars) { +static bool iso9660_valid_string(const char *str, bool allow_a_chars) { /* note that a-chars are not supposed to accept lower case letters, but it looks like common practice * to use them */ return in_charset(str, allow_a_chars ? UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS " _!\"%&'()*+,-./:;<=>?" : UPPERCASE_LETTERS DIGITS "_"); } -int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars) { - if (source && !valid_iso9660_string(source, allow_a_chars)) +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { + if (source && !iso9660_valid_string(source, allow_a_chars)) return -EINVAL; if (source) { @@ -101,16 +101,16 @@ int set_iso9660_string(char target[], size_t len, const char *source, bool allow bool iso9660_volume_name_valid(const char *name) { /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 32; } bool iso9660_system_name_valid(const char *name) { - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 32; } bool iso9660_publisher_name_valid(const char *name) { - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 128; } diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h index 23db9e919a..cdb6eef226 100644 --- a/src/repart/iso9660.h +++ b/src/repart/iso9660.h @@ -158,13 +158,13 @@ struct _packed_ el_torito_section_header { char id_string[28]; }; -void no_iso9660_datetime(struct iso9660_datetime *ret); -int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret); -int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret); -int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars); +void iso9660_datetime_zero(struct iso9660_datetime *ret); +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret); +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret); +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars); -static inline void set_iso9660_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { - assert_se(set_iso9660_string(target, len, source, allow_a_chars) == 0); +static inline void iso9660_set_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert_se(iso9660_set_string(target, len, source, allow_a_chars) == 0); } bool iso9660_volume_name_valid(const char *name); diff --git a/src/repart/repart.c b/src/repart/repart.c index a21ec10da8..121cb22193 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7769,43 +7769,43 @@ static int write_primary_descriptor( } }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); - r = time_to_iso9660_dir_datetime(usec, utc, &desc.root_directory_entry.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &desc.root_directory_entry.time); if (r < 0) return r; - r = set_iso9660_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); if (r < 0) return r; /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ - r = set_iso9660_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); if (r < 0) return r; - set_iso9660_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); - r = set_iso9660_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); if (r < 0) return r; - set_iso9660_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); - set_iso9660_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); - set_iso9660_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); - set_iso9660_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); - set_iso9660_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); + iso9660_set_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); - r = time_to_iso9660_datetime(usec, utc, &desc.volume_creation_date); + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_creation_date); if (r < 0) return r; - r = time_to_iso9660_datetime(usec, utc, &desc.volume_modification_date); + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_modification_date); if (r < 0) return r; - no_iso9660_datetime(&desc.volume_expiration_date); - no_iso9660_datetime(&desc.volume_effective_date); + iso9660_datetime_zero(&desc.volume_expiration_date); + iso9660_datetime_zero(&desc.volume_effective_date); ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE); if (s < 0) @@ -7825,7 +7825,7 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { .boot_catalog_sector = htole32(catalog_sector), }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); strncpy(desc.boot_system_identifier, "EL TORITO SPECIFICATION", sizeof(desc.boot_system_identifier)); @@ -7846,7 +7846,7 @@ static int write_terminal_descriptor(int fd) { }, }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_TERMINAL_DESCRIPTOR*ISO9660_BLOCK_SIZE); if (s < 0) @@ -7929,7 +7929,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector .ident[0] = 0, /* special value for self */ }; - r = time_to_iso9660_dir_datetime(usec, utc, &self.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &self.time); if (r < 0) return r; @@ -7948,7 +7948,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector // TODO: we should probably add some text file explaining there is no content through ISO9660 - r = time_to_iso9660_dir_datetime(usec, utc, &parent.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &parent.time); if (r < 0) return r; From 8953262390fb148803fd2a7aa1e7f994ca6cfd97 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:11:00 +0200 Subject: [PATCH 3/8] iso9660: add extra paranoia when converting dates the ISO9660 date range and the "struct tm" range are quite different, let's add extra paranoia checks that we can always convert the dates without issues or fail cleanly. --- src/repart/iso9660.c | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 3f200942b2..ef10d05bf2 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -21,6 +21,26 @@ void iso9660_datetime_zero(struct iso9660_datetime *ret) { ret->zone = 0; } +static int validate_tm(const struct tm *t) { + assert(t); + + /* Safety checks on bounded fields of struct tm, ranges as per tm(3type). Mostly in place because + * ISO9660 date/time ranges and struct tm ranges differ. */ + + if (t->tm_mon < 0 || t->tm_mon > 11) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Month out of range."); + if (t->tm_mday < 1 || t->tm_mday > 31) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Day of month out of range."); + if (t->tm_hour < 0 || t->tm_hour > 23) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Hour out of range."); + if (t->tm_min < 0 || t->tm_min > 59) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Minute out of range."); + if (t->tm_sec < 0 || t->tm_sec > 60) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Seconds out of range."); + + return 0; +} + int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) { struct tm t; int r; @@ -31,11 +51,19 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r if (r < 0) return r; + r = validate_tm(&t); + if (r < 0) + return r; + if (t.tm_year >= 10000 - 1900) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year has more than 4 digits and is incompatible with ISO9660."); if (t.tm_year + 1900 < 0) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is negative and is incompatible with ISO9660."); + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); + char buf[17]; /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", @@ -43,8 +71,7 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r t.tm_hour, t.tm_min, MIN(t.tm_sec, 59)); memcpy(ret, buf, sizeof(buf)-1); - /* The time zone is encoded by 15 minutes increments */ - ret->zone = t.tm_gmtoff / (15*60); + ret->zone = offset; return 0; } @@ -59,8 +86,16 @@ int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_tim if (r < 0) return r; + r = validate_tm(&t); + if (r < 0) + return r; + if (t.tm_year < 0 || t.tm_year > UINT8_MAX) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Year is incompatible with ISO9660."); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is incompatible with ISO9660."); + + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); *ret = (struct iso9660_dir_time) { .year = t.tm_year, @@ -70,7 +105,7 @@ int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_tim .minute = t.tm_min, .second = MIN(t.tm_sec, 59), /* The time zone is encoded by 15 minutes increments */ - .offset = t.tm_gmtoff / (15*60), + .offset = offset, }; return 0; From d723bb753ecd59d3ce0a82882e13258f7265aa3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:12:44 +0200 Subject: [PATCH 4/8] iso9660: add extra size regarding buffer size --- src/repart/iso9660.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index ef10d05bf2..72d4c285cb 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -65,6 +65,7 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); char buf[17]; + assert_cc(sizeof(buf)-1 == offsetof(struct iso9660_datetime, zone)); /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, From a678bc69f943431ade55238c919dcc6dfe60f811 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:13:13 +0200 Subject: [PATCH 5/8] iso9660: small iso9660_set_string() clean-ups --- src/repart/iso9660.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 72d4c285cb..a555fb9f9e 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -120,15 +120,17 @@ static bool iso9660_valid_string(const char *str, bool allow_a_chars) { } int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { - if (source && !iso9660_valid_string(source, allow_a_chars)) - return -EINVAL; + assert(target || len == 0); if (source) { + if (!iso9660_valid_string(source, allow_a_chars)) + return -EINVAL; + size_t slen = strlen(source); if (slen > len) return -EINVAL; - void *p = mempcpy(target, source, slen); - memset(p, ' ', len - slen); + + memset(mempcpy(target, source, slen), ' ', len - slen); } else memset(target, ' ', len); From 4c1e269f00af97f387740982b2f14baa3c77aaf9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:13:35 +0200 Subject: [PATCH 6/8] iso9660: validate a bunch func params via assert() --- src/repart/repart.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/repart/repart.c b/src/repart/repart.c index 121cb22193..ecd0518530 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7741,6 +7741,8 @@ static int write_primary_descriptor( const char *publisher_id) { int r; + assert(fd >= 0); + struct iso9660_primary_volume_descriptor desc = { .header = { .type = 1, @@ -7817,6 +7819,8 @@ static int write_primary_descriptor( } static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { + assert(fd >= 0); + struct iso9660_eltorito_descriptor desc = { .header = { .type = 0, @@ -7839,6 +7843,8 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { } static int write_terminal_descriptor(int fd) { + assert(fd >= 0); + struct iso9660_terminal_descriptor desc = { .header = { .type = 255, @@ -7858,6 +7864,7 @@ static int write_terminal_descriptor(int fd) { } static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) { + assert(p || size == 0); assert(size % 2 == 0); uint16_t checksum = 0; @@ -7869,6 +7876,8 @@ static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) } static int write_boot_catalog(int fd, uint32_t load_block) { + assert(fd >= 0); + struct el_torito_validation_entry ve = { .header_indicator = 1, .platform = 0xef, /* EFI */ @@ -7914,6 +7923,8 @@ static int write_boot_catalog(int fd, uint32_t load_block) { static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { int r; + assert(fd >= 0); + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ struct iso9660_directory_entry self = { @@ -7970,6 +7981,8 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { int r; + assert(fd >= 0); + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); if (r < 0) return r; From 56245fe67fd1779dc83a20dd3850a8020fefeffa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2026 08:17:29 +0200 Subject: [PATCH 7/8] iso9660: line break overly long parameter list --- src/repart/repart.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index ecd0518530..9ed4815aee 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7920,7 +7920,12 @@ static int write_boot_catalog(int fd, uint32_t load_block) { return 0; } -static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { +static int write_directories( + int fd, + usec_t usec, + bool utc, + uint32_t root_sector) { + int r; assert(fd >= 0); @@ -7978,7 +7983,15 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector return 0; } -static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { +static int write_eltorito( + int fd, + usec_t usec, + bool utc, + uint32_t load_block, /* in iso9660 blocks */ + const char *system_id, + const char *volume_id, + const char *publisher_id) { + int r; assert(fd >= 0); From d2d7bd13db5104b51a830002fee439eb3c94cdfd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2026 08:20:18 +0200 Subject: [PATCH 8/8] update TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 09eeda07ef..37345f4a75 100644 --- a/TODO +++ b/TODO @@ -1354,8 +1354,6 @@ Features: * automatic boot assessment: add one more default success check that just waits for a bit after boot, and blesses the boot if the system stayed up that long. -* systemd-repart: add support for generating ISO9660 images - * systemd-repart: in addition to the existing "factory reset" mode (which simply empties existing partitions marked for that). add a mode where partitions marked for it are entirely removed. Use case: remove secondary OS