mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-18 09:19:17 +01:00
9e79c9d99b
* code cleanup
508 lines
15 KiB
C
508 lines
15 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;
|
|
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
|
|
* 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 )
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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_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 );
|
|
|
|
return ( 0 );
|
|
err_out:
|
|
if ( close_ctx && ctx )
|
|
ntfs_attr_put_search_ctx( ctx );
|
|
return ( -1 );
|
|
}
|
|
|
|
#endif /* HAVE_SETXATTR */
|