/** * efs.c - Limited processing of encrypted files * * This module is part of ntfs-3g library * * Copyright (c) 2009 Martin Bene * 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 * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the main directory of the NTFS-3G * distribution in the file COPYING); if not, write to the Free Software * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_SYSMACROS_H #include #endif #include "types.h" #include "debug.h" #include "attrib.h" #include "inode.h" #include "dir.h" #include "efs.h" #include "index.h" #include "logging.h" #include "misc.h" #include "efs.h" #include "xattrs.h" static ntfschar logged_utility_stream_name[] = { const_cpu_to_le16('$'), const_cpu_to_le16('E'), const_cpu_to_le16('F'), const_cpu_to_le16('S'), const_cpu_to_le16(0) } ; /* * Get the ntfs EFS info into an extended attribute */ int ntfs_get_efs_info(ntfs_inode *ni, char *value, size_t size) { EFS_ATTR_HEADER *efs_info; s64 attr_size = 0; if (ni) { if (ni->flags & FILE_ATTR_ENCRYPTED) { efs_info = (EFS_ATTR_HEADER*)ntfs_attr_readall(ni, AT_LOGGED_UTILITY_STREAM,(ntfschar*)NULL, 0, &attr_size); if (efs_info && (le32_to_cpu(efs_info->length) == attr_size)) { if (attr_size <= (s64)size) { if (value) memcpy(value,efs_info,attr_size); else { errno = EFAULT; attr_size = 0; } } else if (size) { errno = ERANGE; attr_size = 0; } free (efs_info); } else { if (efs_info) { free(efs_info); ntfs_log_error("Bad efs_info for inode %lld\n", (long long)ni->mft_no); } else { ntfs_log_error("Could not get efsinfo" " for inode %lld\n", (long long)ni->mft_no); } errno = EIO; attr_size = 0; } } else { errno = ENODATA; ntfs_log_trace("Inode %lld is not encrypted\n", (long long)ni->mft_no); } } 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; 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); } } 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 * Returns 0, or -1 if there is a problem */ int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, int flags) { int res; int written; ntfs_attr *na; const EFS_ATTR_HEADER *info_header; res = 0; if (ni && value && size) { if (ni->flags & (FILE_ATTR_ENCRYPTED | FILE_ATTR_COMPRESSED)) { if (ni->flags & FILE_ATTR_ENCRYPTED) { ntfs_log_trace("Inode %lld already encrypted\n", (long long)ni->mft_no); errno = EEXIST; } else { /* * Possible problem : if encrypted file was * restored in a compressed directory, it was * restored as compressed. * TODO : decompress first. */ ntfs_log_error("Inode %lld cannot be encrypted and compressed\n", (long long)ni->mft_no); errno = EIO; } return -1; } info_header = (const EFS_ATTR_HEADER*)value; /* make sure we get a likely efsinfo */ if (le32_to_cpu(info_header->length) != size) { errno = EINVAL; return (-1); } if (!ntfs_attr_exist(ni,AT_LOGGED_UTILITY_STREAM, (ntfschar*)NULL,0)) { if (!(flags & XATTR_REPLACE)) { /* * no logged_utility_stream attribute : add one, * apparently, this does not feed the new value in */ res = ntfs_attr_add(ni,AT_LOGGED_UTILITY_STREAM, logged_utility_stream_name,4, (u8*)NULL,(s64)size); } else { errno = ENODATA; res = -1; } } else { errno = EEXIST; res = -1; } if (!res) { /* * open and update the existing efs data */ na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM, logged_utility_stream_name, 4); if (na) { /* resize attribute */ res = ntfs_attr_truncate(na, (s64)size); /* overwrite value if any */ if (!res && value) { written = (int)ntfs_attr_pwrite(na, (s64)0, (s64)size, value); if (written != (s64)size) { ntfs_log_error("Failed to " "update efs data\n"); errno = EIO; res = -1; } } ntfs_attr_close(na); } else res = -1; } if (!res) { /* Don't handle AT_DATA Attribute(s) if inode is a directory */ if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { /* iterate over AT_DATA attributes */ /* set encrypted flag, truncate attribute to match padding bytes */ if (fixup_loop(ni)) return -1; } ni->flags |= FILE_ATTR_ENCRYPTED; NInoSetDirty(ni); NInoFileNameSetDirty(ni); } } else { errno = EINVAL; res = -1; } return (res ? -1 : 0); } /* * Fixup raw encrypted AT_DATA Attribute * read padding length from last two bytes * truncate attribute, make non-resident, * set data size to match padding length * set ATTR_IS_ENCRYPTED flag on attribute * * Return 0 if successful * -1 if failed (errno tells why) */ int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) { s64 newsize; s64 oldsize; le16 appended_bytes; u16 padding_length; ntfs_inode *ni; BOOL close_ctx = FALSE; if (!na) { ntfs_log_error("no na specified for efs_fixup_attribute\n"); goto err_out; } if (!ctx) { ctx = ntfs_attr_get_search_ctx(na->ni, NULL); if (!ctx) { ntfs_log_error("Failed to get ctx for efs\n"); goto err_out; } 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; } } else { if (!NAttrNonResident(na)) { ntfs_log_error("Cannot make non resident" " when a context has been allocated\n"); goto err_out; } } /* 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)) { 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) { ni->data_size = newsize; ni->allocated_size = na->allocated_size; } NInoSetDirty(ni); NInoFileNameSetDirty(ni); ctx->attr->data_size = cpu_to_sle64(newsize); if (sle64_to_cpu(ctx->attr->initialized_size) > newsize) ctx->attr->initialized_size = ctx->attr->data_size; ctx->attr->flags |= ATTR_IS_ENCRYPTED; if (close_ctx) ntfs_attr_put_search_ctx(ctx); return (0); err_out: if (close_ctx && ctx) ntfs_attr_put_search_ctx(ctx); return (-1); }