usbloadergx/source/libntfs/efs.c

508 lines
15 KiB
C
Raw Normal View History

2009-12-19 15:06:57 +01:00
/**
* efs.c - Limited processing of encrypted files
*
* This module is part of ntfs-3g library
2009-12-19 15:06:57 +01:00
*
* Copyright (c) 2009 Martin Bene
* Copyright (c) 2009-2010 Jean-Pierre Andre
2009-12-19 15:06:57 +01:00
*
* 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 )
2009-12-19 15:06:57 +01:00
} ;
/*
* Get the ntfs EFS info into an extended attribute
2009-12-19 15:06:57 +01:00
*/
int ntfs_get_efs_info( ntfs_inode *ni, char *value, size_t size )
{
EFS_ATTR_HEADER *efs_info;
s64 attr_size = 0;
2009-12-19 15:06:57 +01:00
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 );
2009-12-19 15:06:57 +01:00
}
/*
* 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 );
}
2009-12-19 15:06:57 +01:00
/*
* Set the efs data from an extended attribute
* Warning : the new data is not checked
* Returns 0, or -1 if there is a problem
2009-12-19 15:06:57 +01:00
*/
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;
2009-12-19 15:06:57 +01:00
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 );
2009-12-19 15:06:57 +01:00
}
/*
* 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
2009-12-19 15:06:57 +01:00
*
* Return 0 if successful
* -1 if failed (errno tells why)
2009-12-19 15:06:57 +01:00
*/
int ntfs_efs_fixup_attribute( ntfs_attr_search_ctx *ctx, ntfs_attr *na )
{
u64 newsize;
u64 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;
}
}
2009-12-19 15:06:57 +01:00
/* 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 );
2009-12-19 15:06:57 +01:00
ctx->attr->data_size = cpu_to_le64( newsize );
if ( le64_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 );
2009-12-19 15:06:57 +01:00
return ( 0 );
2009-12-19 15:06:57 +01:00
err_out:
if ( close_ctx && ctx )
ntfs_attr_put_search_ctx( ctx );
return ( -1 );
2009-12-19 15:06:57 +01:00
}
#endif /* HAVE_SETXATTR */