diff --git a/include/ntfs-3g/attrib.h b/include/ntfs-3g/attrib.h index 186c9931..43ab7f53 100644 --- a/include/ntfs-3g/attrib.h +++ b/include/ntfs-3g/attrib.h @@ -304,6 +304,7 @@ extern int ntfs_attr_can_be_resident(const ntfs_volume *vol, const ATTR_TYPES type); int ntfs_attr_make_non_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx); +int ntfs_attr_force_non_resident(ntfs_attr *na); extern int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size); extern int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 0e17bc4f..d89da0a6 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -4681,6 +4681,8 @@ static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize); * @newsize: new size (in bytes) to which to resize the attribute * * Change the size of a resident, open ntfs attribute @na to @newsize bytes. + * Can also be used to force an attribute non-resident. In this case, the + * size cannot be changed. * * On success return 0 * On error return values are: @@ -4691,7 +4693,8 @@ static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize); * ERANGE - @newsize is not valid for the attribute type of @na. * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST. */ -static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) +static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize, + BOOL force_non_resident) { ntfs_attr_search_ctx *ctx; ntfs_volume *vol; @@ -4729,7 +4732,7 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) * attribute non-resident if the attribute type supports it. If it is * smaller we can go ahead and attempt the resize. */ - if (newsize < vol->mft_record_size) { + if ((newsize < vol->mft_record_size) && !force_non_resident) { /* Perform the resize of the attribute record. */ if (!(ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, newsize))) { @@ -4769,6 +4772,21 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) if (!ntfs_attr_make_non_resident(na, ctx)) { ntfs_inode_mark_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); + /* + * do not truncate when forcing non-resident, this + * could cause the attribute to be made resident again, + * so size changes are not allowed. + */ + if (force_non_resident) { + ret = 0; + if (newsize != na->data_size) { + ntfs_log_error("Cannot change size when" + " forcing non-resident\n"); + errno = EIO; + ret = STATUS_ERROR; + } + return (ret); + } /* Resize non-resident attribute */ return ntfs_attr_truncate(na, newsize); } else if (errno != ENOSPC && errno != EPERM) { @@ -4817,7 +4835,7 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) ntfs_inode_mark_dirty(tna->ni); ntfs_attr_close(tna); ntfs_attr_put_search_ctx(ctx); - return ntfs_resident_attr_resize(na, newsize); + return ntfs_resident_attr_resize_i(na, newsize, force_non_resident); } /* Check whether error occurred. */ if (errno != ENOENT) { @@ -4837,7 +4855,7 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) ntfs_log_perror("Could not free space in MFT record"); return -1; } - return ntfs_resident_attr_resize(na, newsize); + return ntfs_resident_attr_resize_i(na, newsize, force_non_resident); } /* @@ -4876,7 +4894,7 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_add_attrlist(ni)) return -1; - return ntfs_resident_attr_resize(na, newsize); + return ntfs_resident_attr_resize_i(na, newsize, force_non_resident); } /* Allocate new mft record. */ ni = ntfs_mft_record_alloc(vol, ni); @@ -4897,7 +4915,7 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) ntfs_attr_put_search_ctx(ctx); /* Try to perform resize once again. */ - return ntfs_resident_attr_resize(na, newsize); + return ntfs_resident_attr_resize_i(na, newsize, force_non_resident); resize_done: /* @@ -4918,11 +4936,39 @@ static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize) int ret; ntfs_log_enter("Entering\n"); - ret = ntfs_resident_attr_resize_i(na, newsize); + ret = ntfs_resident_attr_resize_i(na, newsize, FALSE); ntfs_log_leave("\n"); return ret; } +/* + * Force an attribute to be made non-resident without + * changing its size. + * + * This is particularly needed when the attribute has no data, + * as the non-resident variant requires more space in the MFT + * record, and may imply expelling some other attribute. + * + * As a consequence the existing ntfs_attr_search_ctx's have to + * be closed or reinitialized. + * + * returns 0 if successful, + * < 0 if failed, with errno telling why + */ + +int ntfs_attr_force_non_resident(ntfs_attr *na) +{ + int res; + + res = ntfs_resident_attr_resize_i(na, na->data_size, TRUE); + if (!res && !NAttrNonResident(na)) { + res = -1; + errno = EIO; + ntfs_log_error("Failed to force non-resident\n"); + } + return (res); +} + /** * ntfs_attr_make_resident - convert a non-resident to a resident attribute * @na: open ntfs attribute to make resident diff --git a/libntfs-3g/efs.c b/libntfs-3g/efs.c index 0af1a4df..6ccec20a 100644 --- a/libntfs-3g/efs.c +++ b/libntfs-3g/efs.c @@ -4,7 +4,7 @@ * This module is part of ntfs-3g library * * Copyright (c) 2009 Martin Bene - * Copyright (c) 2009 Jean-Pierre Andre + * Copyright (c) 2009-2010 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -120,6 +120,93 @@ int ntfs_get_efs_info(ntfs_inode *ni, char *value, size_t size) return (attr_size ? (int)attr_size : -errno); } +/* + * Fix all encrypted AT_DATA attributes of an inode + * + * The fix may require making an attribute non resident, which + * requires more space in the MFT record, and may cause some + * attribute to be expelled and the full record to be reorganized. + * When this happens, the search for data attributes has to be + * reinitialized. + * + * Returns zero if successful. + * -1 if there is a problem. + */ + +static int fixup_loop(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *ctx; + ntfs_attr *na; + ATTR_RECORD *a; + BOOL restart; + BOOL first; + int cnt; + int maxcnt; + int res = 0; + + maxcnt = 0; + do { + restart = FALSE; + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get ctx for efs\n"); + res = -1; + } + cnt = 0; + while (!restart && !res + && !ntfs_attr_lookup(AT_DATA, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + cnt++; + a = ctx->attr; + na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA, + (ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)), + a->name_length); + if (!na) { + ntfs_log_error("can't open DATA Attribute\n"); + res = -1; + } + if (na && !(ctx->attr->flags & ATTR_IS_ENCRYPTED)) { + if (!NAttrNonResident(na) + && ntfs_attr_make_non_resident(na, ctx)) { + /* + * ntfs_attr_make_non_resident fails if there + * is not enough space in the MFT record. + * When this happens, force making non-resident + * so that some other attribute is expelled. + */ + if (ntfs_attr_force_non_resident(na)) { + res = -1; + } else { + /* make sure there is some progress */ + if (cnt <= maxcnt) { + errno = EIO; + ntfs_log_error("Multiple failure" + " making non resident\n"); + res = -1; + } else { + ntfs_attr_put_search_ctx(ctx); + ctx = (ntfs_attr_search_ctx*)NULL; + restart = TRUE; + maxcnt = cnt; + } + } + } + if (!restart && !res + && ntfs_efs_fixup_attribute(ctx, na)) { + ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n"); + res = -1; + } + } + if (na) + ntfs_attr_close(na); + } + first = FALSE; + } while (restart && !res); + if (ctx) + ntfs_attr_put_search_ctx(ctx); + return (res); +} + /* * Set the efs data from an extended attribute * Warning : the new data is not checked @@ -134,7 +221,6 @@ int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, int written; ntfs_attr *na; const EFS_ATTR_HEADER *info_header; - ntfs_attr_search_ctx *ctx; res = 0; if (ni && value && size) { @@ -210,20 +296,8 @@ int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, /* iterate over AT_DATA attributes */ /* set encrypted flag, truncate attribute to match padding bytes */ - ctx = ntfs_attr_get_search_ctx(ni, NULL); - if (!ctx) { - ntfs_log_error("Failed to get ctx for efs\n"); - return (-1); - } - while (!ntfs_attr_lookup(AT_DATA, NULL, 0, - CASE_SENSITIVE, 0, NULL, 0, ctx)) { - if (ntfs_efs_fixup_attribute(ctx, NULL)) { - ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n"); - ntfs_attr_put_search_ctx(ctx); - return(-1); - } - } - ntfs_attr_put_search_ctx(ctx); + if (fixup_loop(ni)) + return -1; } ni->flags |= FILE_ATTR_ENCRYPTED; NInoSetDirty(ni); @@ -250,15 +324,14 @@ int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) { u64 newsize; + u64 oldsize; le16 appended_bytes; u16 padding_length; - ATTR_RECORD *a; ntfs_inode *ni; - BOOL close_na = FALSE; BOOL close_ctx = FALSE; - if (!ctx && !na) { - ntfs_log_error("neither ctx nor na specified for efs_fixup_attribute\n"); + if (!na) { + ntfs_log_error("no na specified for efs_fixup_attribute\n"); goto err_out; } if (!ctx) { @@ -267,55 +340,79 @@ int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) ntfs_log_error("Failed to get ctx for efs\n"); goto err_out; } - close_ctx=TRUE; + close_ctx = TRUE; if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) { ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); goto err_out; } - } - - a = ctx->attr; - if (!na) { - na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA, - (ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)), - a->name_length); - if (!na) { - ntfs_log_error("can't open DATA Attribute\n"); - return (-1); + } else { + if (!NAttrNonResident(na)) { + ntfs_log_error("Cannot make non resident" + " when a context has been allocated\n"); + goto err_out; } - close_na = TRUE; - } - /* make sure size is valid for a raw encrypted stream */ - if ((na->data_size & 511) != 2) { - ntfs_log_error("Bad raw encrypted stream\n"); - goto err_out; - } - /* read padding length from last two bytes of attribute */ - if (ntfs_attr_pread(na, na->data_size-2, 2, &appended_bytes) != 2) { - ntfs_log_error("Error reading padding length\n"); - goto err_out; - } - padding_length = le16_to_cpu(appended_bytes); - if (padding_length > 511 || padding_length > na->data_size-2) { - errno = EINVAL; - ntfs_log_error("invalid padding length %d for data_size %lld\n", - padding_length, (long long)na->data_size); - goto err_out; - } - newsize = na->data_size - padding_length - 2; - /* truncate attribute to possibly free clusters allocated - for the last two bytes */ - if (ntfs_attr_truncate(na, na->data_size-2)) { - ntfs_log_error("Error truncating attribute\n"); - goto err_out; } - /* Encrypted AT_DATA Attributes MUST be non-resident */ + /* no extra bytes are added to void attributes */ + oldsize = na->data_size; + if (oldsize) { + /* make sure size is valid for a raw encrypted stream */ + if ((oldsize & 511) != 2) { + ntfs_log_error("Bad raw encrypted stream\n"); + goto err_out; + } + /* read padding length from last two bytes of attribute */ + if (ntfs_attr_pread(na, oldsize - 2, 2, &appended_bytes) != 2) { + ntfs_log_error("Error reading padding length\n"); + goto err_out; + } + padding_length = le16_to_cpu(appended_bytes); + if (padding_length > 511 || padding_length > na->data_size-2) { + errno = EINVAL; + ntfs_log_error("invalid padding length %d for data_size %lld\n", + padding_length, (long long)oldsize); + goto err_out; + } + newsize = oldsize - padding_length - 2; + /* + * truncate attribute to possibly free clusters allocated + * for the last two bytes, but do not truncate to new size + * to avoid losing useful data + */ + if (ntfs_attr_truncate(na, oldsize - 2)) { + ntfs_log_error("Error truncating attribute\n"); + goto err_out; + } + } else + newsize = 0; + + /* + * Encrypted AT_DATA Attributes MUST be non-resident + * This has to be done after the attribute is resized, as + * resizing down to zero may cause the attribute to be made + * resident. + */ if (!NAttrNonResident(na) - && ntfs_attr_make_non_resident(na, ctx)) { - ntfs_log_error("Error making DATA attribute non-resident\n"); - goto err_out; + && ntfs_attr_make_non_resident(na, ctx)) { + if (!close_ctx + || ntfs_attr_force_non_resident(na)) { + ntfs_log_error("Error making DATA attribute non-resident\n"); + goto err_out; + } else { + /* + * must reinitialize context after forcing + * non-resident. We need a context for updating + * the state, and at this point, we are sure + * the context is not used elsewhere. + */ + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); + goto err_out; + } + } } ni = na->ni; if (!na->name_len) { @@ -324,8 +421,6 @@ int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) } NInoSetDirty(ni); NInoFileNameSetDirty(ni); - if (close_na) - ntfs_attr_close(na); ctx->attr->data_size = cpu_to_le64(newsize); if (le64_to_cpu(ctx->attr->initialized_size) > newsize) @@ -336,8 +431,6 @@ int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) return (0); err_out: - if (close_na && na) - ntfs_attr_close(na); if (close_ctx && ctx) ntfs_attr_put_search_ctx(ctx); return (-1); diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 9ea1eff1..7d12c1c5 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -695,7 +695,9 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, * encrypted files to include padding required for decryption * also include 2 bytes for padding info */ - if (ctx->efs_raw && ni->flags & FILE_ATTR_ENCRYPTED) + if (ctx->efs_raw + && (ni->flags & FILE_ATTR_ENCRYPTED) + && ni->data_size) stbuf->st_size = ((ni->data_size + 511) & ~511) + 2; #endif /* HAVE_SETXATTR */ /* @@ -1325,8 +1327,10 @@ static void ntfs_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, max_read = na->data_size; #ifdef HAVE_SETXATTR /* extended attributes interface required */ /* limit reads at next 512 byte boundary for encrypted attributes */ - if (ctx->efs_raw && (na->data_flags & ATTR_IS_ENCRYPTED) && - NAttrNonResident(na)) { + if (ctx->efs_raw + && max_read + && (na->data_flags & ATTR_IS_ENCRYPTED) + && NAttrNonResident(na)) { max_read = ((na->data_size+511) & ~511) + 2; } #endif /* HAVE_SETXATTR */ @@ -3069,10 +3073,11 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, goto exit; } rsize = na->data_size; - if (ctx->efs_raw && - (na->data_flags & ATTR_IS_ENCRYPTED) && - NAttrNonResident(na)) - rsize = ((na->data_size + 511) & ~511)+2; + if (ctx->efs_raw + && rsize + && (na->data_flags & ATTR_IS_ENCRYPTED) + && NAttrNonResident(na)) + rsize = ((na->data_size + 511) & ~511) + 2; if (size) { if (size >= (size_t)rsize) { value = (char*)ntfs_malloc(rsize); @@ -3311,6 +3316,7 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } } total = 0; + res = 0; if (size) { do { part = ntfs_attr_pwrite(na, total, size - total, @@ -3318,20 +3324,20 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, if (part > 0) total += part; } while ((part > 0) && (total < size)); - if (total != size) - res = -errno; - else - if (!(res = ntfs_attr_pclose(na))) - if (ctx->efs_raw - && (ni->flags & FILE_ATTR_ENCRYPTED)) - res = ntfs_efs_fixup_attribute(NULL, - na); - if (total && !(ni->flags & FILE_ATTR_ARCHIVE)) { - set_archive(ni); - NInoFileNameSetDirty(ni); + } + if ((total != size) || ntfs_attr_pclose(na)) + res = -errno; + else { + if (ctx->efs_raw + && (ni->flags & FILE_ATTR_ENCRYPTED)) { + if (ntfs_efs_fixup_attribute(NULL,na)) + res = -errno; } - } else - res = 0; + } + if (!res && !(ni->flags & FILE_ATTR_ARCHIVE)) { + set_archive(ni); + NInoFileNameSetDirty(ni); + } exit: if (na) ntfs_attr_close(na); diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 23d6043f..fe491f84 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -768,7 +768,9 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) * encrypted files to include padding required for decryption * also include 2 bytes for padding info */ - if (ctx->efs_raw && ni->flags & FILE_ATTR_ENCRYPTED) + if (ctx->efs_raw + && (ni->flags & FILE_ATTR_ENCRYPTED) + && ni->data_size) stbuf->st_size = ((ni->data_size + 511) & ~511) + 2; #endif /* HAVE_SETXATTR */ /* @@ -1230,8 +1232,10 @@ static int ntfs_fuse_read(const char *org_path, char *buf, size_t size, max_read = na->data_size; #ifdef HAVE_SETXATTR /* extended attributes interface required */ /* limit reads at next 512 byte boundary for encrypted attributes */ - if (ctx->efs_raw && (na->data_flags & ATTR_IS_ENCRYPTED) && - NAttrNonResident(na)) { + if (ctx->efs_raw + && max_read + && (na->data_flags & ATTR_IS_ENCRYPTED) + && NAttrNonResident(na)) { max_read = ((na->data_size+511) & ~511) + 2; } #endif /* HAVE_SETXATTR */ @@ -2984,10 +2988,11 @@ static int ntfs_fuse_getxattr(const char *path, const char *name, goto exit; } rsize = na->data_size; - if (ctx->efs_raw && - (na->data_flags & ATTR_IS_ENCRYPTED) && - NAttrNonResident(na)) - rsize = ((na->data_size + 511) & ~511)+2; + if (ctx->efs_raw + && rsize + && (na->data_flags & ATTR_IS_ENCRYPTED) + && NAttrNonResident(na)) + rsize = ((na->data_size + 511) & ~511) + 2; if (size) { if (size >= (size_t)rsize) { res = ntfs_attr_pread(na, 0, rsize, value); @@ -3235,6 +3240,7 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, } } total = 0; + res = 0; if (size) { do { part = ntfs_attr_pwrite(na, total, size - total, @@ -3242,20 +3248,20 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, if (part > 0) total += part; } while ((part > 0) && (total < size)); - if (total != size) - res = -errno; - else - if (!(res = ntfs_attr_pclose(na))) - if (ctx->efs_raw - && (ni->flags & FILE_ATTR_ENCRYPTED)) - res = ntfs_efs_fixup_attribute(NULL, - na); - if (total && !(ni->flags & FILE_ATTR_ARCHIVE)) { - set_archive(ni); - NInoFileNameSetDirty(ni); + } + if ((total != size) || ntfs_attr_pclose(na)) + res = -errno; + else { + if (ctx->efs_raw + && (ni->flags & FILE_ATTR_ENCRYPTED)) { + if (ntfs_efs_fixup_attribute(NULL,na)) + res = -errno; } - } else - res = 0; + } + if (!res && !(ni->flags & FILE_ATTR_ARCHIVE)) { + set_archive(ni); + NInoFileNameSetDirty(ni); + } exit: if (na) ntfs_attr_close(na);