📄 file.c
字号:
/****************************************************************************
* *
* File Stream I/O Functions *
* Copyright Peter Gutmann 1993-2007 *
* *
****************************************************************************/
#if defined( __UNIX__ ) && defined( __linux__ )
/* In order for the fileReadonly() check to work we need to be able to
check errno, however for this to work the headers that specify that
threading is being used must be the first headers included
(specifically, the include order has to be pthread.h, unistd.h,
everything else) or errno.h, which is pulled in by stdlib.h, gets
set up as an extern int rather than a function */
#include "crypt.h"
#endif /* Older Linux broken include-file dependencies */
#include <stdarg.h>
#if defined( INC_ALL )
#include "stream_int.h"
#include "file.h"
#else
#include "io/stream_int.h"
#include "io/file.h"
#endif /* Compiler-specific includes */
/* In order to get enhanced control over things like file security and
buffering we can't use stdio but have to rely on using OS-level file
routines, which is essential for working with things like ACL's for
sensitive files and forcing disk writes for files we want to erase.
Without the forced disk write the data in the cache doesn't get flushed
before the file delete request arrives, after which it's discarded rather
than being written, so the file never gets overwritten. In addition some
embedded environments don't support stdio so we have to supply our own
alternatives.
When implementing the following for new systems there are certain things
that you need to ensure to guarantee error-free operation:
- File permissions should be set as indicated by the file open flags.
- File sharing controls (shared vs. exclusive access locks) should be
implemented.
- If the file is locked for exclusive access, the open call should either
block until the lock is released (they're never held for more than a
fraction of a second) or return CRYPT_ERROR_TIMEOUT depending on how
the OS handles locks.
When erasing data, we may run into problems on embedded systems using
solid-state storage that implements wear-levelling by using a log-
structured filesystem (LFS) type arrangement. These work by never
writing a sector twice but always appending newly-written data at the
next free location until the volume is full, at which point a garbage
collector runs to reclaim. A main goal of LFS's is speed (data is
written in large sequential writes rather than lots of small random
writes) and error-recovery by taking advantage of the characteristics
of the log structure, however a side-effect of the write mechanism is
that it makes wear-levelling management quite simple. However, the use
of a LFS also makes it impossible to reliably overwrite data, since
new writes never touch the existing data. There's no easy way to cope
with this since we have no way of telling what the underlying media is
doing with our data. A mediating factor though is that embedded systems
are usually sealed, single-use systems where the chances of a second user
accessing the data is low. The only possible threat then is post system-
retirement recovery of the data, presumably if it contains valuable data
it'll be disposed of appropriately */
/* Symbolic defines for stdio-style file access modes */
#if defined( DDNAME_IO )
#pragma convlit( suspend )
#define MODE_READ "rb,byteseek"
#define MODE_WRITE "wb,byteseek,recfm=*"
#define MODE_READWRITE "rb+,byteseek,recfm=*"
#pragma convlit( resume )
#else
#if defined( EBCDIC_CHARS )
#pragma convlit( suspend )
#define MODE_READ "rb"
#define MODE_WRITE "wb"
#define MODE_READWRITE "rb+"
#pragma convlit( resume )
#else
#define MODE_READ "rb"
#define MODE_WRITE "wb"
#define MODE_READWRITE "rb+"
#endif /* EBCDIC_CHARS */
#endif /* Standard vs. DDNAME I/O */
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Append a filename to a path and add the suffix */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 4 ) ) \
static int appendFilename( OUT_BUFFER( pathMaxLen, *pathLen ) char *path,
IN_LENGTH_SHORT const int pathMaxLen,
OUT_LENGTH_SHORT_Z int *pathLen,
IN_BUFFER( fileNameLen ) const char *fileName,
IN_LENGTH_SHORT const int fileNameLen,
IN_ENUM( BUILDPATH_OPTION ) \
const BUILDPATH_OPTION_TYPE option )
{
const int partialPathLen = strlen( path );
assert( isWritePtr( path, pathMaxLen ) );
assert( isWritePtr( pathLen, sizeof( int ) ) );
assert( isReadPtr( fileName, fileNameLen ) );
REQUIRES( pathMaxLen > 8 && pathMaxLen < MAX_INTLENGTH_SHORT );
REQUIRES( fileNameLen > 0 && fileNameLen < MAX_INTLENGTH_SHORT );
REQUIRES( option > BUILDPATH_NONE && option < BUILDPATH_LAST );
/* Clear return value */
*pathLen = 0;
#ifdef EBCDIC_CHARS
#pragma convlit( suspend )
#endif /* EBCDIC_CHARS */
/* If we're using a fixed filename it's quite simple, just append it
and we're done */
if( option == BUILDPATH_RNDSEEDFILE )
{
if( partialPathLen + 12 > pathMaxLen )
return( CRYPT_ERROR_OVERFLOW );
memcpy( path + partialPathLen, "randseed.dat", 12 );
*pathLen = partialPathLen + 12;
return( CRYPT_OK );
}
/* User-defined filenames are a bit more complex because we have to
safely append a variable-length quantity to the path */
if( partialPathLen + fileNameLen + 4 > pathMaxLen )
return( CRYPT_ERROR_OVERFLOW );
memcpy( path + partialPathLen, fileName, fileNameLen );
memcpy( path + partialPathLen + fileNameLen, ".p15", 4 );
*pathLen = partialPathLen + fileNameLen + 4;
#ifdef EBCDIC_CHARS
#pragma convlit( resume )
#endif /* EBCDIC_CHARS */
return( CRYPT_OK );
}
/****************************************************************************
* *
* AMX File Stream Functions *
* *
****************************************************************************/
#if defined( __AMX__ )
/* Open/close a file stream */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int sFileOpen( INOUT STREAM *stream, IN_STRING const char *fileName,
IN_FLAGS( FILE ) const int mode )
{
static const int modes[] = {
FJ_O_RDONLY, FJ_O_RDONLY,
FJ_O_WRONLY | FJ_O_CREAT | FJ_O_NOSHAREANY,
FJ_O_RDWR | FJ_O_NOSHAREWR
};
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( fileName != NULL );
REQUIRES( mode != 0 );
/* Initialise the stream structure */
memset( stream, 0, sizeof( STREAM ) );
stream->type = STREAM_TYPE_FILE;
if( ( mode & FILE_FLAG_RW_MASK ) == FILE_FLAG_READ )
stream->flags = STREAM_FLAG_READONLY;
openMode = modes[ mode & FILE_FLAG_RW_MASK ];
/* If we're trying to write to the file, check whether we've got
permission to do so */
if( ( mode & FILE_FLAG_WRITE ) && fileReadonly( fileName ) )
return( CRYPT_ERROR_PERMISSION );
/* Try and open the file */
stream->fd = fjopen( fileName, openMode, ( openMode & FJ_O_CREAT ) ? \
FJ_S_IREAD | FJ_S_IWRITE : 0 );
if( stream->fd < 0 )
{
const int errNo = fjfserrno();
return( ( errNo == FJ_EACCES || errNo == FJ_ESHARE ) ? \
CRYPT_ERROR_PERMISSION : \
( errNo == FJ_ENOENT ) ? \
CRYPT_ERROR_NOTFOUND : CRYPT_ERROR_OPEN );
}
return( CRYPT_OK );
}
RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int sFileClose( INOUT STREAM *stream )
{
assert( isWritePtr( stream, sizeof( STREAM ) ) );
REQUIRES( stream->type == STREAM_TYPE_FILE );
/* Close the file and clear the stream structure */
fjclose( stream->fd );
zeroise( stream, sizeof( STREAM ) );
return( CRYPT_OK );
}
/* Read/write a block of data from/to a file stream */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
int fileRead( INOUT STREAM *stream,
OUT_BUFFER( length, *bytesRead ) void *buffer,
IN_LENGTH const int length,
OUT_LENGTH_Z int *bytesRead )
{
int byteCount;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( buffer, length ) );
assert( isWritePtr( bytesRead, sizeof( int ) ) );
REQUIRES( stream->type == STREAM_TYPE_FILE );
REQUIRES( length > 0 && length < MAX_INTLENGTH );
/* Clear return value */
*bytesRead = 0;
if( ( byteCount = fjread( stream->fd, buffer, length ) ) < 0 )
return( sSetError( stream, CRYPT_ERROR_READ ) );
*bytesRead = byteCount;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int fileWrite( INOUT STREAM *stream,
IN_BUFFER( length ) const void *buffer,
IN_LENGTH const int length )
{
int bytesWritten;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( buffer, length ) );
REQUIRES( stream->type == STREAM_TYPE_FILE );
REQUIRES( length > 0 && length < MAX_INTLENGTH );
if( ( bytesWritten = fjwrite( stream->fd, buffer, length ) ) < 0 || \
bytesWritten != length )
return( sSetError( stream, CRYPT_ERROR_WRITE ) );
return( CRYPT_OK );
}
/* Commit data in a file stream to backing storage */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int fileFlush( INOUT STREAM *stream )
{
assert( isWritePtr( stream, sizeof( STREAM ) ) );
REQUIRES( stream->type == STREAM_TYPE_FILE );
fjflush( stream->fd );
}
/* Change the read/write position in a file */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int fileSeek( INOUT STREAM *stream, IN_LENGTH_Z const long position )
{
assert( isWritePtr( stream, sizeof( STREAM ) ) );
REQUIRES( stream->type == STREAM_TYPE_FILE );
REQUIRES( position >= 0 && position < MAX_INTLENGTH );
if( fjlseek( stream->fd, position, FJ_SEEK_SET ) < 0 )
return( sSetError( stream, CRYPT_ERROR_READ ) );
return( CRYPT_OK );
}
/* Check whether a file is writeable */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
BOOLEAN fileReadonly( IN_STRING const char *fileName )
{
struct fjxstat fileInfo;
assert( fileName != NULL );
if( fjstat( fileName, &fileInfo ) < 0 )
return( TRUE );
return( ( fileInfo->_xxx ) ? TRUE : FALSE );
}
/* File deletion functions: Wipe a file from the current position to EOF,
and wipe and delete a file (although it's not terribly rigorous).
Vestigia nulla retrorsum */
static void eraseFile( const STREAM *stream, long position, long length )
{
assert( isReadPtr( stream, sizeof( STREAM ) ) );
REQUIRES_V( stream->type == STREAM_TYPE_FILE );
REQUIRES_V( position >= 0 && position < MAX_INTLENGTH );
REQUIRES_V( length > 0 && length < MAX_INTLENGTH );
/* Wipe everything past the current position in the file */
while( length > 0 )
{
MESSAGE_DATA msgData;
BYTE buffer[ ( BUFSIZ * 2 ) + 8 ];
int bytesToWrite = min( length, BUFSIZ * 2 );
/* We need to make sure that we fill the buffer with random data for
each write, otherwise compressing filesystems will just compress
it to nothing */
setMessageData( &msgData, buffer, bytesToWrite );
krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
if( fjwrite( stream->fd, buffer, bytesToWrite ) < 0 )
break; /* An error occurred while writing, exit */
length -= bytesToWrite;
}
fjchsize( stream->fd, position );
}
STDC_NONNULL_ARG( ( 1 ) ) \
void fileClearToEOF( const STREAM *stream )
{
struct fjxstat fileInfo;
int length, position;
assert( isReadPtr( stream, sizeof( STREAM ) ) );
REQUIRES_V( stream->type == STREAM_TYPE_FILE );
/* Wipe everything past the current position in the file */
if( fjstat( fileName, &fileInfo ) < 0 )
return;
length = fileInfo._xxx;
if( ( position = fjtell( stream->fd ) ) < 0 )
return;
length -= position;
if( length <= 0 )
return; /* Nothing to do, exit */
eraseFile( stream, position, length );
}
STDC_NONNULL_ARG( ( 1 ) ) \
void fileErase( IN_STRING const char *fileName )
{
STREAM stream;
struct fjxstat fileInfo;
int status;
assert( fileName != NULL );
/* Try and open the file so that we can erase it. If this fails, the
best that we can do is a straight unlink */
status = sFileOpen( &stream, fileName,
FILE_FLAG_READ | FILE_FLAG_WRITE | \
FILE_FLAG_EXCLUSIVE_ACCESS );
if( cryptStatusError( status ) )
{
remove( fileName );
return;
}
/* Determine the size of the file and erase it */
fjstat( fileName, &fileInfo );
eraseFile( &stream, 0, fileInfo._xxx );
/* Reset the file's attributes */
fjfattr( stream.fd, FJ_DA_NORMAL );
/* Delete the file */
sFileClose( &stream );
fjunlink( fileName );
}
/* Build the path to a file in the cryptlib directory */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 4 ) ) \
int fileBuildCryptlibPath( OUT_BUFFER( pathMaxLen, pathLen ) char *path,
IN_LENGTH_SHORT const int pathMaxLen,
OUT_LENGTH_SHORT_Z int *pathLen,
IN_BUFFER( fileNameLen ) const char *fileName,
IN_LENGTH_SHORT const int fileNameLen,
IN_ENUM( BUILDPATH_OPTION ) \
const BUILDPATH_OPTION_TYPE option )
{
assert( isWritePtr( path, pathMaxLen ) );
assert( isWritePtr( pathLen, sizeof( int ) ) );
assert( isReadPtr( fileName, fileNameLen ) );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -