X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flib%2Fustar.c;fp=src%2Flib%2Fustar.c;h=49af69a1116fb738b275acfcd6c3ceba39049b25;hb=59f738d500f51ffc5f487344865b8bed69c26281;hp=0000000000000000000000000000000000000000;hpb=0fd3a243b790dd1cfc9e0a40c57dddde56cf344d;p=pintos-anon diff --git a/src/lib/ustar.c b/src/lib/ustar.c new file mode 100644 index 0000000..49af69a --- /dev/null +++ b/src/lib/ustar.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +/* Header for ustar-format tar archive. See the documentation of + the "pax" utility in [SUSv3] for the the "ustar" format + specification. */ +struct ustar_header + { + char name[100]; /* File name. Null-terminated if room. */ + char mode[8]; /* Permissions as octal string. */ + char uid[8]; /* User ID as octal string. */ + char gid[8]; /* Group ID as octal string. */ + char size[12]; /* File size in bytes as octal string. */ + char mtime[12]; /* Modification time in seconds + from Jan 1, 1970, as octal string. */ + char chksum[8]; /* Sum of octets in header as octal string. */ + char typeflag; /* An enum ustar_type value. */ + char linkname[100]; /* Name of link target. + Null-terminated if room. */ + char magic[6]; /* "ustar\0" */ + char version[2]; /* "00" */ + char uname[32]; /* User name, always null-terminated. */ + char gname[32]; /* Group name, always null-terminated. */ + char devmajor[8]; /* Device major number as octal string. */ + char devminor[8]; /* Device minor number as octal string. */ + char prefix[155]; /* Prefix to file name. + Null-terminated if room. */ + char padding[12]; /* Pad to 512 bytes. */ + } +PACKED; + +/* Returns the checksum for the given ustar format HEADER. */ +static unsigned int +calculate_chksum (const struct ustar_header *h) +{ + const uint8_t *header = (const uint8_t *) h; + unsigned int chksum; + size_t i; + + chksum = 0; + for (i = 0; i < USTAR_HEADER_SIZE; i++) + { + /* The ustar checksum is calculated as if the chksum field + were all spaces. */ + const size_t chksum_start = offsetof (struct ustar_header, chksum); + const size_t chksum_end = chksum_start + sizeof h->chksum; + bool in_chksum_field = i >= chksum_start && i < chksum_end; + chksum += in_chksum_field ? ' ' : header[i]; + } + return chksum; +} + +/* Drop possibly dangerous prefixes from FILE_NAME and return the + stripped name. An archive with file names that start with "/" + or "../" could cause a naive tar extractor to write to + arbitrary parts of the file system, not just the destination + directory. We don't want to create such archives or be such a + naive extractor. + + The return value can be a suffix of FILE_NAME or a string + literal. */ +static const char * +strip_antisocial_prefixes (const char *file_name) +{ + while (*file_name == '/' + || !memcmp (file_name, "./", 2) + || !memcmp (file_name, "../", 3)) + file_name = strchr (file_name, '/') + 1; + return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; +} + +/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive + header in ustar format for a SIZE-byte file named FILE_NAME of + the given TYPE. The caller is responsible for writing the + header to a file or device. + + If successful, returns true. On failure (due to an + excessively long file name), returns false. */ +bool +ustar_make_header (const char *file_name, enum ustar_type type, + int size, char header[USTAR_HEADER_SIZE]) +{ + struct ustar_header *h = (struct ustar_header *) header; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); + + /* Check file name. */ + file_name = strip_antisocial_prefixes (file_name); + if (strlen (file_name) > 99) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + /* Fill in header except for final checksum. */ + memset (h, 0, sizeof *h); + strlcpy (h->name, file_name, sizeof h->name); + snprintf (h->mode, sizeof h->mode, "%07o", + type == USTAR_REGULAR ? 0644 : 0755); + strlcpy (h->uid, "0000000", sizeof h->uid); + strlcpy (h->gid, "0000000", sizeof h->gid); + snprintf (h->size, sizeof h->size, "%011o", size); + snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); + h->typeflag = type; + strlcpy (h->magic, "ustar", sizeof h->magic); + h->version[0] = h->version[1] = '0'; + strlcpy (h->gname, "root", sizeof h->gname); + strlcpy (h->uname, "root", sizeof h->uname); + + /* Compute and fill in final checksum. */ + snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); + + return true; +} + +/* Parses a SIZE-byte octal field in S in the format used by + ustar format. If successful, stores the field's value in + *VALUE and returns true; on failure, returns false. + + ustar octal fields consist of a sequence of octal digits + terminated by a space or a null byte. The ustar specification + seems ambiguous as to whether these fields must be padded on + the left with '0's, so we accept any field that fits in the + available space, regardless of whether it fills the space. */ +static bool +parse_octal_field (const char *s, size_t size, unsigned long int *value) +{ + size_t ofs; + + *value = 0; + for (ofs = 0; ofs < size; ofs++) + { + char c = s[ofs]; + if (c >= '0' && c <= '7') + { + if (*value > ULONG_MAX / 8) + { + /* Overflow. */ + return false; + } + *value = c - '0' + *value * 8; + } + else if (c == ' ' || c == '\0') + { + /* End of field, but disallow completely empty + fields. */ + return ofs > 0; + } + else + { + /* Bad character. */ + return false; + } + } + + /* Field did not end in space or null byte. */ + return false; +} + +/* Returns true if the CNT bytes starting at BLOCK are all zero, + false otherwise. */ +static bool +is_all_zeros (const char *block, size_t cnt) +{ + while (cnt-- > 0) + if (*block++ != 0) + return false; + return true; +} + +/* Parses HEADER as a ustar-format archive header for a regular + file or directory. If successful, stores the archived file's + name in *FILE_NAME (as a pointer into HEADER or a string + literal), its type in *TYPE, and its size in bytes in *SIZE, + and returns a null pointer. On failure, returns a + human-readable error message. */ +const char * +ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, enum ustar_type *type, int *size) +{ + const struct ustar_header *h = (const struct ustar_header *) header; + unsigned long int chksum, size_ul; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + + /* Detect end of archive. */ + if (is_all_zeros (header, USTAR_HEADER_SIZE)) + { + *file_name = NULL; + *type = USTAR_EOF; + *size = 0; + return NULL; + } + + /* Validate ustar header. */ + if (memcmp (h->magic, "ustar", 6)) + return "not a ustar archive"; + else if (h->version[0] != '0' || h->version[1] != '0') + return "invalid ustar version"; + else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) + return "corrupt chksum field"; + else if (chksum != calculate_chksum (h)) + return "checksum mismatch"; + else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') + return "file name too long"; + else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) + return "unimplemented file type"; + if (h->typeflag == USTAR_REGULAR) + { + if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) + return "corrupt file size field"; + else if (size_ul > INT_MAX) + return "file too large"; + } + else + size_ul = 0; + + /* Success. */ + *file_name = strip_antisocial_prefixes (h->name); + *type = h->typeflag; + *size = size_ul; + return NULL; +} +