/*
* security.c
*
- * Read the security data from the WIM. Doing anything with the security data
- * is not yet implemented other than printing some information about it.
- *
- * Copyright (C) 2012 Eric Biggers
- *
- * wimlib - Library for working with WIM files
+ * Read and write the per-WIM-image table of security descriptors.
+ */
+
+/*
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
*
- * This library is free software; you can redistribute it and/or modify it under
+ * This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option) any
+ * Software Foundation; either version 3 of the License, or (at your option) any
* later version.
*
- * This library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
*
- * You should have received a copy of the GNU Lesser General Public License along
- * with this library; if not, write to the Free Software Foundation, Inc., 59
- * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this file; if not, see http://www.gnu.org/licenses/.
*/
-#include "wimlib_internal.h"
-#include "io.h"
-#include "security.h"
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
-#ifdef ENABLE_SECURITY_DATA
+#include "wimlib/assert.h"
+#include "wimlib/avl_tree.h"
+#include "wimlib/endianness.h"
+#include "wimlib/error.h"
+#include "wimlib/security.h"
+#include "wimlib/sha1.h"
+#include "wimlib/util.h"
-/*
- * Reads the security data from the metadata resource.
+struct wim_security_data_disk {
+ le32 total_length;
+ le32 num_entries;
+ le64 sizes[];
+} _packed_attribute;
+
+struct wim_security_data *
+new_wim_security_data(void)
+{
+ return CALLOC(1, sizeof(struct wim_security_data));
+}
+
+/*
+ * Reads the security data from the metadata resource of a WIM image.
*
- * @metadata_resource: An array that contains the uncompressed metadata
- * resource for the WIM file.
- * @metadata_resource_len: The length of @metadata_resource.
- * @sd_p: A pointer to a pointer wim_security_data structure that will be filled
- * in with a pointer to a new wim_security_data structure on success.
+ * @buf
+ * Buffer containing an uncompressed WIM metadata resource.
+ * @buf_len
+ * Length of the uncompressed metadata resource, in bytes.
+ * @sd_ret
+ * On success, a pointer to the resulting security data structure will be
+ * returned here.
*
* Note: There is no `offset' argument because the security data is located at
* the beginning of the metadata resource.
+ *
+ * Return values:
+ * WIMLIB_ERR_SUCCESS (0)
+ * WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ * WIMLIB_ERR_NOMEM
*/
-int read_security_data(const u8 metadata_resource[],
- u64 metadata_resource_len, struct wim_security_data **sd_p)
+int
+read_wim_security_data(const u8 *buf, size_t buf_len,
+ struct wim_security_data **sd_ret)
{
struct wim_security_data *sd;
- const u8 *p;
+ int ret;
+ u64 total_len;
u64 sizes_size;
+ u64 size_no_descriptors;
+ const struct wim_security_data_disk *sd_disk;
+ const u8 *p;
- if (metadata_resource_len < 8) {
- ERROR("Not enough space in %"PRIu64"-byte file resource for "
- "security data!\n", metadata_resource_len);
- return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
- }
- sd = MALLOC(sizeof(struct wim_security_data));
+ if (buf_len < 8)
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+
+ sd = new_wim_security_data();
if (!sd)
- return WIMLIB_ERR_NOMEM;
- p = get_u32(metadata_resource, &sd->total_length);
- p = get_u32(p, &sd->num_entries);
+ goto out_of_memory;
+
+ sd_disk = (const struct wim_security_data_disk *)buf;
+ sd->total_length = le32_to_cpu(sd_disk->total_length);
+ sd->num_entries = le32_to_cpu(sd_disk->num_entries);
+
+ DEBUG("Reading security data: num_entries=%u, total_length=%u",
+ sd->num_entries, sd->total_length);
+
+ /* Length field of 0 is a special case that really means length
+ * of 8. */
+ if (sd->total_length == 0)
+ sd->total_length = 8;
+
+ /* The security_id field of each dentry is a signed 32-bit integer, so
+ * the possible indices into the security descriptors table are 0
+ * through 0x7fffffff. Which means 0x80000000 security descriptors
+ * maximum. Not like you should ever have anywhere close to that many
+ * security descriptors! */
+ if (sd->num_entries > 0x80000000)
+ goto out_invalid_sd;
/* Verify the listed total length of the security data is big enough to
* include the sizes array, verify that the file data is big enough to
- * include it as well, then allocate the array of sizes. */
- sizes_size = sd->num_entries * sizeof(u64);
+ * include it as well, then allocate the array of sizes.
+ *
+ * Note: The total length of the security data must fit in a 32-bit
+ * integer, even though each security descriptor size is a 64-bit
+ * integer. This is stupid, and we need to be careful not to actually
+ * let the security descriptor sizes be over 0xffffffff. */
+ if (sd->total_length > buf_len)
+ goto out_invalid_sd;
- DEBUG("Reading security data with %u entries\n", sd->num_entries);
+ sizes_size = (u64)sd->num_entries * sizeof(u64);
+ size_no_descriptors = 8 + sizes_size;
+ if (size_no_descriptors > sd->total_length)
+ goto out_invalid_sd;
- if (sd->num_entries == 0) {
- FREE(sd);
- return 0;
- }
+ total_len = size_no_descriptors;
- u64 size_no_descriptors = 8 + sizes_size;
- if (size_no_descriptors > sd->total_length) {
- ERROR("Security data total length of %"PRIu64" is too short because\n"
- "there must be at least %"PRIu64" bytes of security "
- "data!\n", sd->total_length,
- 8 + sizes_size);
- FREE(sd);
- return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
- }
- if (size_no_descriptors > metadata_resource_len) {
- ERROR("File resource of %"PRIu64" bytes is not big enough\n"
- "to hold security data of at least %"PRIu64" "
- "bytes!\n", metadata_resource_len, size_no_descriptors);
- FREE(sd);
- return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
- }
+ /* Return immediately if no security descriptors. */
+ if (sd->num_entries == 0)
+ goto out_align_total_length;
+
+ /* Allocate a new buffer for the sizes array */
sd->sizes = MALLOC(sizes_size);
- if (!sd->sizes) {
- FREE(sd);
- return WIMLIB_ERR_NOMEM;
+ if (!sd->sizes)
+ goto out_of_memory;
+
+ /* Copy the sizes array into the new buffer */
+ for (u32 i = 0; i < sd->num_entries; i++) {
+ sd->sizes[i] = le64_to_cpu(sd_disk->sizes[i]);
+ if (sd->sizes[i] > 0xffffffff)
+ goto out_invalid_sd;
}
- /* Copy the sizes array in from the file data. */
- p = get_bytes(p, sizes_size, sd->sizes);
- array_to_le64(sd->sizes, sd->num_entries);
+ p = (const u8*)sd_disk + size_no_descriptors;
- /* Allocate the array of pointers to descriptors, and read them in. */
- sd->descriptors = CALLOC(sd->num_entries, sizeof(u8*));
- if (!sd->descriptors) {
- FREE(sd);
- FREE(sd->sizes);
- return WIMLIB_ERR_NOMEM;
- }
- u64 total_len = size_no_descriptors;
+ /* Allocate the array of pointers to the security descriptors, then read
+ * them into separate buffers. */
+ sd->descriptors = CALLOC(sd->num_entries, sizeof(sd->descriptors[0]));
+ if (!sd->descriptors)
+ goto out_of_memory;
- for (uint i = 0; i < sd->num_entries; i++) {
+ for (u32 i = 0; i < sd->num_entries; i++) {
+ if (sd->sizes[i] == 0)
+ continue;
total_len += sd->sizes[i];
- if (total_len > sd->total_length) {
- ERROR("Security data total length of %"PRIu64" is too "
- "short because there are at least %"PRIu64" "
- "bytes of security data!\n",
- sd->total_length, total_len);
- free_security_data(sd);
- return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
- }
- if (total_len > metadata_resource_len) {
- ERROR("File resource of %"PRIu64" bytes is not big enough "
- "to hold security data of at least %"PRIu64" "
- "bytes!\n", metadata_resource_len, total_len);
- free_security_data(sd);
- return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
- }
- sd->descriptors[i] = MALLOC(sd->sizes[i]);
- if (!sd->descriptors[i]) {
- free_security_data(sd);
- return WIMLIB_ERR_NOMEM;
- }
- p = get_bytes(p, sd->sizes[i], sd->descriptors[i]);
+ if (total_len > (u64)sd->total_length)
+ goto out_invalid_sd;
+ sd->descriptors[i] = memdup(p, sd->sizes[i]);
+ if (!sd->descriptors[i])
+ goto out_of_memory;
+ p += sd->sizes[i];
+ }
+out_align_total_length:
+ total_len = ALIGN(total_len, 8);
+ sd->total_length = ALIGN(sd->total_length, 8);
+ if (total_len != sd->total_length) {
+ WARNING("Expected WIM security data total length of "
+ "%u bytes, but calculated %u bytes",
+ sd->total_length, (unsigned)total_len);
}
- sd->refcnt = 1;
- *sd_p = sd;
- return 0;
+ *sd_ret = sd;
+ ret = 0;
+ goto out;
+out_invalid_sd:
+ ERROR("WIM security data is invalid!");
+ ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+ goto out_free_sd;
+out_of_memory:
+ ERROR("Out of memory while reading WIM security data!");
+ ret = WIMLIB_ERR_NOMEM;
+out_free_sd:
+ free_wim_security_data(sd);
+out:
+ return ret;
}
-/*
- * Writes security data to an in-memory buffer.
+/*
+ * Writes the security data for a WIM image to an in-memory buffer.
*/
-u8 *write_security_data(const struct wim_security_data *sd, u8 *p)
+u8 *
+write_wim_security_data(const struct wim_security_data * restrict sd,
+ u8 * restrict p)
{
- if (sd) {
- DEBUG("Writing security data (total_length = %u, "
- "num_entries = %u)\n", sd->total_length,
- sd->num_entries);
- u8 *orig_p = p;
- p = put_u32(p, sd->total_length);
- p = put_u32(p, sd->num_entries);
+ DEBUG("Writing security data (total_length = %"PRIu32", num_entries "
+ "= %"PRIu32")", sd->total_length, sd->num_entries);
+
+ u8 *orig_p = p;
+ struct wim_security_data_disk *sd_disk = (struct wim_security_data_disk*)p;
+ u32 num_entries = sd->num_entries;
- for (uint i = 0; i < sd->num_entries; i++)
- p = put_u64(p, sd->sizes[i]);
+ sd_disk->total_length = cpu_to_le32(sd->total_length);
+ sd_disk->num_entries = cpu_to_le32(num_entries);
- for (uint i = 0; i < sd->num_entries; i++)
- p = put_bytes(p, sd->sizes[i], sd->descriptors[i]);
+ for (u32 i = 0; i < num_entries; i++)
+ sd_disk->sizes[i] = cpu_to_le64(sd->sizes[i]);
- wimlib_assert(p - orig_p <= sd->total_length);
+ p = (u8*)&sd_disk->sizes[num_entries];
- DEBUG("Successfully wrote security data.\n");
- return orig_p + sd->total_length;
- } else {
- DEBUG("Writing security data (total_length = 8, "
- "num_entries = 0)\n");
- p = put_u32(p, 8);
- return put_u32(p, 0);
+ for (u32 i = 0; i < num_entries; i++)
+ p = mempcpy(p, sd->descriptors[i], sd->sizes[i]);
+ while ((uintptr_t)p & 7)
+ *p++ = 0;
+
+ wimlib_assert(p - orig_p == sd->total_length);
+
+ DEBUG("Successfully wrote security data.");
+ return p;
+}
+
+void
+free_wim_security_data(struct wim_security_data *sd)
+{
+ if (sd) {
+ u8 **descriptors = sd->descriptors;
+ u32 num_entries = sd->num_entries;
+ if (descriptors)
+ while (num_entries--)
+ FREE(*descriptors++);
+ FREE(sd->sizes);
+ FREE(sd->descriptors);
+ FREE(sd);
}
}
-/* XXX We don't actually do anything with the ACL's yet besides being able to
- * print a few things. It seems it would be a lot of work to have comprehensive
- * support for all the weird flags and stuff, and Windows PE seems to be okay
- * running from a WIM file that doesn't have any security data at all... */
+struct sd_node {
+ s32 security_id;
+ u8 hash[SHA1_HASH_SIZE];
+ struct avl_tree_node index_node;
+};
+
+#define SD_NODE(avl_node) \
+ avl_tree_entry(avl_node, struct sd_node, index_node)
-static void print_acl(const u8 *p)
+static void
+free_sd_tree(struct avl_tree_node *node)
{
- ACL *acl = (ACL*)p;
- TO_LE16(acl->acl_size);
- TO_LE16(acl->acl_count);
- printf(" [ACL]\n");
- printf(" Revision = %u\n", acl->revision);
- printf(" ACL Size = %u\n", acl->acl_size);
- printf(" ACE Count = %u\n", acl->ace_count);
-
- p += sizeof(ACL);
- for (uint i = 0; i < acl->ace_count; i++) {
- ACEHeader *hdr = (ACEHeader*)p;
- printf(" [ACE]\n");
- printf(" ACE type = %d\n", hdr->type);
- printf(" ACE flags = 0x%x\n", hdr->flags);
- printf(" ACE size = %u\n", hdr->size);
- AccessAllowedACE *aaa = (AccessAllowedACE*)hdr;
- printf(" ACE mask = %x\n", to_le32(aaa->mask));
- printf(" SID start = %u\n", to_le32(aaa->sid_start));
- p += hdr->size;
+ if (node) {
+ free_sd_tree(node->left);
+ free_sd_tree(node->right);
+ FREE(SD_NODE(node));
}
}
-static void print_sid(const u8 *p)
+void
+rollback_new_security_descriptors(struct wim_sd_set *sd_set)
+{
+ struct wim_security_data *sd = sd_set->sd;
+ u8 **descriptors = sd->descriptors + sd_set->orig_num_entries;
+ u32 num_entries = sd->num_entries - sd_set->orig_num_entries;
+ while (num_entries--)
+ FREE(*descriptors++);
+ sd->num_entries = sd_set->orig_num_entries;
+}
+
+/* Frees a security descriptor index set. */
+void
+destroy_sd_set(struct wim_sd_set *sd_set)
+{
+ free_sd_tree(sd_set->root);
+}
+
+static int
+_avl_cmp_nodes_by_hash(const struct avl_tree_node *n1,
+ const struct avl_tree_node *n2)
{
- SID *sid = (SID*)p;
- printf(" [SID]\n");
- printf(" Revision = %u\n", sid->revision);
- printf(" Subauthority count = %u\n", sid->sub_authority_count);
- printf(" Identifier authority = ");
- print_byte_field(sid->identifier_authority, sizeof(sid->identifier_authority));
- putchar('\n');
- for (uint i = 0; i < sid->sub_authority_count; i++)
- printf(" Subauthority %u = %u\n", i, to_le32(sid->sub_authority[i]));
+ return hashes_cmp(SD_NODE(n1)->hash, SD_NODE(n2)->hash);
}
-static void print_security_descriptor(const u8 *p, u64 size)
+/* Inserts a new node into the security descriptor index tree. Returns true
+ * if successful (not a duplicate). */
+static bool
+insert_sd_node(struct wim_sd_set *set, struct sd_node *new)
{
- SecurityDescriptor *sd = (SecurityDescriptor*)p;
- TO_LE16(sd->security_descriptor_control);
- TO_LE32(sd->owner_offset);
- TO_LE32(sd->group_offset);
- TO_LE32(sd->sacl_offset);
- TO_LE32(sd->dacl_offset);
- printf("Revision = %u\n", sd->revision);
- printf("Security Descriptor Control = %u\n", sd->security_descriptor_control);
- printf("Owner offset = %u\n", sd->owner_offset);
- printf("Group offset = %u\n", sd->group_offset);
- printf("System ACL offset = %u\n", sd->sacl_offset);
- printf("Discretionary ACL offset = %u\n", sd->dacl_offset);
-
- if (sd->owner_offset != 0)
- print_sid(p + sd->owner_offset);
- if (sd->group_offset != 0)
- print_sid(p + sd->group_offset);
- if (sd->sacl_offset != 0)
- print_acl(p + sd->sacl_offset);
- if (sd->dacl_offset != 0)
- print_acl(p + sd->dacl_offset);
+ return NULL == avl_tree_insert(&set->root, &new->index_node,
+ _avl_cmp_nodes_by_hash);
}
-/*
- * Prints the security data for a WIM file.
+/* Returns the index of the security descriptor having a SHA1 message digest of
+ * @hash. If not found, return -1. */
+static s32
+lookup_sd(struct wim_sd_set *set, const u8 hash[SHA1_HASH_SIZE])
+{
+ struct avl_tree_node *res;
+ struct sd_node dummy;
+
+ copy_hash(dummy.hash, hash);
+ res = avl_tree_lookup_node(set->root, &dummy.index_node,
+ _avl_cmp_nodes_by_hash);
+ if (!res)
+ return -1;
+ return SD_NODE(res)->security_id;
+}
+
+/*
+ * Adds a security descriptor to the indexed security descriptor set as well as
+ * the corresponding `struct wim_security_data', and returns the new security
+ * ID; or, if there is an existing security descriptor that is the same, return
+ * the security ID for it. If a new security descriptor cannot be allocated,
+ * return -1.
*/
-void print_security_data(const struct wim_security_data *sd)
+s32
+sd_set_add_sd(struct wim_sd_set *sd_set, const char *descriptor, size_t size)
{
- puts("[SECURITY DATA]");
- if (sd) {
- printf("Length = %u bytes\n", sd->total_length);
- printf("Number of Entries = %u\n", sd->num_entries);
-
- u64 num_entries = (u64)sd->num_entries;
- for (u64 i = 0; i < num_entries; i++) {
- printf("[SecurityDescriptor %"PRIu64", "
- "length = %"PRIu64"]\n",
- i, sd->sizes[i]);
- print_security_descriptor(sd->descriptors[i],
- sd->sizes[i]);
- putchar('\n');
- }
- } else {
- puts("Length = 8 bytes\n"
- "Number of Entries = 0");
- return;
- }
- putchar('\n');
+ u8 hash[SHA1_HASH_SIZE];
+ s32 security_id;
+ struct sd_node *new;
+ u8 **descriptors;
+ u64 *sizes;
+ u8 *descr_copy;
+ struct wim_security_data *sd;
+ bool bret;
+
+ sha1_buffer(descriptor, size, hash);
+
+ security_id = lookup_sd(sd_set, hash);
+ if (security_id >= 0) /* Identical descriptor already exists */
+ goto out;
+
+ /* Need to add a new security descriptor */
+ security_id = -1;
+
+ new = MALLOC(sizeof(*new));
+ if (!new)
+ goto out;
+
+ descr_copy = memdup(descriptor, size);
+ if (!descr_copy)
+ goto out_free_node;
+
+ sd = sd_set->sd;
+ new->security_id = sd->num_entries;
+ copy_hash(new->hash, hash);
+
+ /* There typically are only a few dozen security descriptors in a
+ * directory tree, so expanding the array of security descriptors by
+ * only 1 extra space each time should not be a problem. */
+ descriptors = REALLOC(sd->descriptors,
+ (sd->num_entries + 1) * sizeof(sd->descriptors[0]));
+ if (!descriptors)
+ goto out_free_descr;
+ sd->descriptors = descriptors;
+ sizes = REALLOC(sd->sizes,
+ (sd->num_entries + 1) * sizeof(sd->sizes[0]));
+ if (!sizes)
+ goto out_free_descr;
+ sd->sizes = sizes;
+ sd->descriptors[sd->num_entries] = descr_copy;
+ sd->sizes[sd->num_entries] = size;
+ sd->num_entries++;
+ DEBUG("There are now %u security descriptors", sd->num_entries);
+ bret = insert_sd_node(sd_set, new);
+ wimlib_assert(bret);
+ security_id = new->security_id;
+ goto out;
+out_free_descr:
+ FREE(descr_copy);
+out_free_node:
+ FREE(new);
+out:
+ return security_id;
}
-void free_security_data(struct wim_security_data *sd)
+/* Initialize a `struct sd_set' mapping from SHA1 message digests of security
+ * descriptors to indices into the security descriptors table of the WIM image
+ * (security IDs). */
+int
+init_sd_set(struct wim_sd_set *sd_set, struct wim_security_data *sd)
{
- if (!sd)
- return;
- wimlib_assert(sd->refcnt >= 1);
- if (sd->refcnt == 1) {
- u8 **descriptors = sd->descriptors;
- u32 num_entries = sd->num_entries;
+ int ret;
- if (descriptors)
- while (num_entries--)
- FREE(*descriptors++);
- FREE(sd->sizes);
- FREE(sd->descriptors);
- FREE(sd);
- } else {
- sd->refcnt--;
+ sd_set->sd = sd;
+ sd_set->root = NULL;
+
+ /* Remember the original number of security descriptors so that newly
+ * added ones can be rolled back if needed. */
+ sd_set->orig_num_entries = sd->num_entries;
+ for (u32 i = 0; i < sd->num_entries; i++) {
+ struct sd_node *new;
+
+ new = MALLOC(sizeof(struct sd_node));
+ if (!new) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_destroy_sd_set;
+ }
+ sha1_buffer(sd->descriptors[i], sd->sizes[i], new->hash);
+ new->security_id = i;
+ if (!insert_sd_node(sd_set, new))
+ FREE(new); /* Ignore duplicate security descriptor */
}
+ ret = 0;
+ goto out;
+out_destroy_sd_set:
+ destroy_sd_set(sd_set);
+out:
+ return ret;
}
-
-#endif