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 diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 5bc9588e68..a555fb9f9e 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,27 @@ 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) { +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,25 +51,33 @@ int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret 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]; + 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, 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; } -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; @@ -59,8 +87,16 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time 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,29 +106,31 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time .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; } -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)) - return -EINVAL; +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { + 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); @@ -101,16 +139,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 7a51928fd6..cdb6eef226 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]; @@ -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..9ed4815aee 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, @@ -7769,43 +7771,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) @@ -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, @@ -7825,7 +7829,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)); @@ -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, @@ -7846,7 +7852,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) @@ -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 */ @@ -7911,9 +7920,16 @@ 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); + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ struct iso9660_directory_entry self = { @@ -7929,7 +7945,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 +7964,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; @@ -7967,9 +7983,19 @@ 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); + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); if (r < 0) return r;