mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-12-12 03:15:40 +08:00
33cb3087b5
These variable are only ever assigned to/from s64 values, so their type should be s64, not u64. This fixes a compiler warning about signed/unsigned comparison.
438 lines
11 KiB
C
438 lines
11 KiB
C
/**
|
|
* 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 <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SETXATTR
|
|
#include <sys/xattr.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#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"
|
|
|
|
#ifdef HAVE_SETXATTR /* extended attributes interface required */
|
|
|
|
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);
|
|
}
|
|
|
|
#endif /* HAVE_SETXATTR */
|