diff --git a/ChangeLog b/ChangeLog index a49d76a1..68c1565e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,3 @@ +ChangeLog can be found at : -Detailed ChangeLog can be found at - http://www.tuxera.com/community/release-history/ - -The changes and history may also be found on the source repository : - http://sourceforge.net/p/ntfs-3g/ntfs-3g/ci/edge/tree/ + https://github.com/tuxera/ntfs-3g/wiki diff --git a/NEWS b/NEWS index 3a7effde..1040fd8b 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,3 @@ +Project information can be found at : -Project news are at http://tuxera.com/community/ntfs-3g-download/ - -Release notes are maintained at http://tuxera.com/community/release-history/ - + https://github.com/tuxera/ntfs-3g/ diff --git a/README b/README index b829fd0b..9962af40 100644 --- a/README +++ b/README @@ -3,9 +3,10 @@ INTRODUCTION ============ The NTFS-3G driver is an open source, freely available read/write NTFS driver -for Linux, FreeBSD, Mac OS X, NetBSD, OpenSolaris, QNX and Haiku. It provides +for Linux, FreeBSD, macOS, NetBSD, OpenIndiana, QNX and Haiku. It provides safe and fast handling of the Windows XP, Windows Server 2003, Windows 2000, -Windows Vista, Windows Server 2008 and Windows 7 file systems. +Windows Vista, Windows Server 2008, Windows 7, Windows 8, Windows Server 2012, +Windows Server 2016, Windows 10 and Windows Server 2019 NTFS file systems. The purpose of the project is to develop, quality assurance and support a trustable, featureful and high performance solution for hardware platforms @@ -18,21 +19,22 @@ Besides the common file system features, NTFS-3G has support for file ownership and permissions, POSIX ACLs, junction points, extended attributes and creating internally compressed files (parameter files in the directory .NTFS-3G may be required to enable them). The new compressed file formats -available in Windows 10 can also be read through a plugin. For using -advanced features, please get the instructions from - - http://www.tuxera.com/community/ntfs-3g-advanced/ +available in Windows 10 can also be read through a plugin. News, support answers, problem submission instructions, support and discussion -forums, performance numbers and other information are available on the project -web site at +forums, and other information are available on the project web site at + + https://github.com/tuxera/ntfs-3g + +The project has been funded, supported and maintained since 2008 by Tuxera: + + https://tuxera.com - http://www.tuxera.com/community/ LICENSES ======== -All the NTFS related components : the file system drivers, the ntfsprogs +All the NTFS related components: the file system drivers, the ntfsprogs utilities and the shared library libntfs-3g are distributed 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 @@ -41,6 +43,7 @@ version. See the included file COPYING. The fuse-lite library is distributed under the terms of the GNU LGPLv2. See the included file COPYING.LIB. + QUICK INSTALLATION ================== @@ -73,12 +76,6 @@ There are also a few make targets for building parts : make drivers : only build drivers and libraries, without ntfsprogs make ntfsprogs : only build ntfsprogs and libntfs-3g, without drivers -Non-Linux: Please see - - http://www.tuxera.com/community/ntfs-3g-download/ - -for known OS specific installation and source packages, but generally -the same procedures apply. USAGE ===== @@ -106,23 +103,23 @@ TESTING WITHOUT INSTALLING Newer versions of ntfs-3g can be tested without installing anything and without disturbing an existing installation. Just configure and make as shown previously. This will create the scripts ntfs-3g and lowntfs-3g -in the src directory, which you may activate for testing : +in the src directory, which you may activate for testing: ./configure make -then, as root : +then, as root: src/ntfs-3g [-o mount-options] /dev/sda1 /mnt/windows -And, to end the test, unmount the usual way : +And, to end the test, unmount the usual way: umount /dev/sda1 NTFS UTILITIES ============== -The ntfsprogs includes utilities for doing all required tasks to NTFS -partitions. In general, just run a utility without any command line +The ntfsprogs directory includes utilities for doing all required tasks to +NTFS partitions. In general, just run a utility without any command line options to display the version number and usage syntax. The following utilities are so far implemented: @@ -159,6 +156,6 @@ ntfscat - Concatenate files and print their contents on the standard output. ntfscp - Overwrite files on an NTFS partition. -ntfssecaudit : audit the security metadata. +ntfssecaudit - Audit the security metadata. -ntfsusermap : assistance for building a user mapping file. +ntfsusermap - Assistance for building a user mapping file. diff --git a/configure.ac b/configure.ac index 7d8324f5..029b334f 100644 --- a/configure.ac +++ b/configure.ac @@ -24,8 +24,8 @@ # Autoconf AC_PREREQ(2.59) -AC_INIT([ntfs-3g],[2017.3.23],[ntfs-3g-devel@lists.sf.net]) -LIBNTFS_3G_VERSION="88" +AC_INIT([ntfs-3g],[2021.8.22],[ntfs-3g-devel@lists.sf.net]) +LIBNTFS_3G_VERSION="89" AC_CONFIG_SRCDIR([src/ntfs-3g.c]) # Environment diff --git a/include/ntfs-3g/attrib.h b/include/ntfs-3g/attrib.h index b3752a60..f2a180c9 100644 --- a/include/ntfs-3g/attrib.h +++ b/include/ntfs-3g/attrib.h @@ -398,6 +398,7 @@ extern int ntfs_attr_data_write(ntfs_inode *ni, const char *buf, size_t size, off_t offset); extern int ntfs_attr_shrink_size(ntfs_inode *ni, ntfschar *stream_name, int stream_name_len, off_t offset); +extern int ntfs_attr_inconsistent(const ATTR_RECORD *a, const MFT_REF mref); #endif /* defined _NTFS_ATTRIB_H */ diff --git a/include/ntfs-3g/index.h b/include/ntfs-3g/index.h index 036b742f..d001863a 100644 --- a/include/ntfs-3g/index.h +++ b/include/ntfs-3g/index.h @@ -139,6 +139,10 @@ extern ntfs_index_context *ntfs_index_ctx_get(ntfs_inode *ni, extern void ntfs_index_ctx_put(ntfs_index_context *ictx); extern void ntfs_index_ctx_reinit(ntfs_index_context *ictx); +extern int ntfs_index_block_inconsistent(const INDEX_BLOCK *ib, u32 block_size, + u64 inum, VCN vcn); +extern int ntfs_index_entry_inconsistent(const INDEX_ENTRY *ie, + COLLATION_RULES collation_rule, u64 inum); extern int ntfs_index_lookup(const void *key, const int key_len, ntfs_index_context *ictx) __attribute_warn_unused_result__; diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index 30e07906..42800a28 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -117,6 +117,7 @@ typedef enum { NV_HideDotFiles, /* 1: Set hidden flag on dot files */ NV_Compression, /* 1: allow compression */ NV_NoFixupWarn, /* 1: Do not log fixup errors */ + NV_FreeSpaceKnown, /* 1: The free space is now known */ } ntfs_volume_state_bits; #define test_nvol_flag(nv, flag) test_bit(NV_##flag, (nv)->state) @@ -155,6 +156,10 @@ typedef enum { #define NVolSetNoFixupWarn(nv) set_nvol_flag(nv, NoFixupWarn) #define NVolClearNoFixupWarn(nv) clear_nvol_flag(nv, NoFixupWarn) +#define NVolFreeSpaceKnown(nv) test_nvol_flag(nv, FreeSpaceKnown) +#define NVolSetFreeSpaceKnown(nv) set_nvol_flag(nv, FreeSpaceKnown) +#define NVolClearFreeSpaceKnown(nv) clear_nvol_flag(nv, FreeSpaceKnown) + /* * NTFS version 1.1 and 1.2 are used by Windows NT4. * NTFS version 2.x is used by Windows 2000 Beta diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c index e05924ea..d811d108 100644 --- a/libntfs-3g/acls.c +++ b/libntfs-3g/acls.c @@ -1330,6 +1330,10 @@ struct POSIX_SECURITY *ntfs_build_basic_posix( pydesc->acccnt = 3; pydesc->defcnt = 0; pydesc->firstdef = 6; + pydesc->filler = 0; + pydesc->acl.version = POSIX_VERSION; + pydesc->acl.flags = 0; + pydesc->acl.filler = 0; } else errno = ENOMEM; return (pydesc); diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 1283b6b4..18883a5d 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -5,7 +5,7 @@ * Copyright (c) 2002-2005 Richard Russon * Copyright (c) 2002-2008 Szabolcs Szakacsits * Copyright (c) 2004-2007 Yura Pakhuchiy - * Copyright (c) 2007-2020 Jean-Pierre Andre + * Copyright (c) 2007-2021 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -489,6 +489,17 @@ ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, } if (a->non_resident) { + if ((!le16_andz(a->flags, ATTR_COMPRESSION_MASK) + || a->compression_unit) + && (ni->vol->major_ver < 3)) { + errno = EIO; + ntfs_log_perror("Compressed inode %lld not allowed" + " on NTFS %d.%d", + (unsigned long long)ni->mft_no, + ni->vol->major_ver, + ni->vol->major_ver); + goto put_err_out; + } if (!le16_andz(a->flags, ATTR_COMPRESSION_MASK) && !a->compression_unit) { errno = EIO; @@ -497,6 +508,17 @@ ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, (unsigned long long)ni->mft_no, le32_to_cpu(type)); goto put_err_out; } + if (!le16_andz(a->flags, ATTR_COMPRESSION_MASK) + && (a->compression_unit + != STANDARD_COMPRESSION_UNIT)) { + errno = EIO; + ntfs_log_perror("Compressed inode %lld attr 0x%lx has " + "an unsupported compression unit %d", + (unsigned long long)ni->mft_no, + (long)le32_to_cpu(type), + (int)a->compression_unit); + goto put_err_out; + } ntfs_attr_init(na, TRUE, a->flags, !le16_andz(a->flags, ATTR_IS_ENCRYPTED), !le16_andz(a->flags, ATTR_IS_SPARSE), @@ -1256,6 +1278,17 @@ static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, LCN lcn_seek_from = -1; VCN cur_vcn, from_vcn; + if (na->ni->mft_no == FILE_Bitmap) { + /* + * Filling a hole in the main bitmap implies allocating + * clusters, which is likely to imply updating the + * bitmap in a cluster being allocated. + * Not supported now, could lead to endless recursions. + */ + ntfs_log_error("Corrupt $BitMap not fully allocated\n"); + errno = EIO; + goto err_out; + } to_write = min(count, ((*rl)->length << vol->cluster_size_bits) - *ofs); cur_vcn = (*rl)->vcn; @@ -2765,6 +2798,8 @@ static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name, ATTR_RECORD *a; ntfs_volume *vol; ntfschar *upcase; + ptrdiff_t offs; + ptrdiff_t space; u32 upcase_len; ntfs_log_trace("attribute type 0x%x.\n", le32_to_cpu(type)); @@ -2794,8 +2829,17 @@ static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name, a = (ATTR_RECORD*)((char*)ctx->attr + le32_to_cpu(ctx->attr->length)); for (;; a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length))) { - if (p2n(a) < p2n(ctx->mrec) || (char*)a > (char*)ctx->mrec + - le32_to_cpu(ctx->mrec->bytes_allocated)) + /* + * Make sure the attribute fully lies within the MFT record + * and we can safely access its minimal fields. + */ + offs = p2n(a) - p2n(ctx->mrec); + space = le32_to_cpu(ctx->mrec->bytes_in_use) - offs; + if ((offs < 0) + || (((space < (ptrdiff_t)offsetof(ATTR_RECORD, + resident_end)) + || (space < (ptrdiff_t)le32_to_cpu(a->length))) + && ((space < 4) || !le32_eq(a->type, AT_END)))) break; ctx->attr = a; if ((!le32_eq(type, AT_UNUSED) && (le32_to_cpu(a->type) > @@ -2824,6 +2868,16 @@ static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name, } } else { register int rc; + + if (a->name_length + && ((le16_to_cpu(a->name_offset) + + a->name_length * sizeof(ntfschar)) + > le32_to_cpu(a->length))) { + ntfs_log_error("Corrupt attribute name" + " in MFT record %lld\n", + (long long)ctx->ntfs_ino->mft_no); + break; + } if (name && ((rc = ntfs_names_full_collate(name, name_len, (ntfschar*)((char*)a + le16_to_cpu(a->name_offset)), @@ -2986,6 +3040,8 @@ static int ntfs_external_attr_find(ATTR_TYPES type, const ntfschar *name, u8 *al_start, *al_end; ATTR_RECORD *a; ntfschar *al_name; + ptrdiff_t offs; + ptrdiff_t space; u32 al_name_len; BOOL is_first_search = FALSE; @@ -3026,8 +3082,22 @@ static int ntfs_external_attr_find(ATTR_TYPES type, const ntfschar *name, le32_to_cpu(AT_ATTRIBUTE_LIST)) goto find_attr_list_attr; } else { + /* Check for small entry */ + if (((p2n(al_end) - p2n(ctx->al_entry)) + < (long)offsetof(ATTR_LIST_ENTRY, name)) + || (le16_to_cpu(ctx->al_entry->length) & 7) + || (le16_to_cpu(ctx->al_entry->length) + < offsetof(ATTR_LIST_ENTRY, name))) + goto corrupt; + al_entry = (ATTR_LIST_ENTRY*)((char*)ctx->al_entry + le16_to_cpu(ctx->al_entry->length)); + if ((u8*)al_entry == al_end) + goto not_found; + /* Preliminary check for small entry */ + if ((p2n(al_end) - p2n(al_entry)) + < (long)offsetof(ATTR_LIST_ENTRY, name)) + goto corrupt; /* * If this is an enumeration and the attribute list attribute * is the next one in the enumeration sequence, just return the @@ -3090,11 +3160,18 @@ find_attr_list_attr: /* Catch the end of the attribute list. */ if ((u8*)al_entry == al_end) goto not_found; - if (le16_cmpz(al_entry->length)) - break; - if ((u8*)al_entry + 6 > al_end || (u8*)al_entry + - le16_to_cpu(al_entry->length) > al_end) - break; + + if ((((u8*)al_entry + offsetof(ATTR_LIST_ENTRY, name)) > al_end) + || ((u8*)al_entry + le16_to_cpu(al_entry->length) > al_end) + || (le16_to_cpu(al_entry->length) & 7) + || (le16_to_cpu(al_entry->length) + < offsetof(ATTR_LIST_ENTRY, name_length)) + || (al_entry->name_length + && ((u8*)al_entry + al_entry->name_offset + + al_entry->name_length * sizeof(ntfschar)) + > al_end)) + break; /* corrupt */ + next_al_entry = (ATTR_LIST_ENTRY*)((u8*)al_entry + le16_to_cpu(al_entry->length)); if (!le32_eq(type, AT_UNUSED)) { @@ -3174,6 +3251,12 @@ is_enumeration: ctx->mrec = ctx->base_mrec; } else { /* We want an extent record. */ + if (!vol->mft_na) { + ntfs_log_perror("$MFT not ready for " + "opening an extent to inode %lld\n", + (long long)base_ni->mft_no); + break; + } ni = ntfs_extent_inode_open(base_ni, al_entry->mft_reference); if (!ni) @@ -3202,12 +3285,18 @@ is_enumeration: * with the same meanings as above. */ do_next_attr_loop: - if ((char*)a < (char*)ctx->mrec || (char*)a > (char*)ctx->mrec + - le32_to_cpu(ctx->mrec->bytes_allocated)) + /* + * Make sure the attribute fully lies within the MFT record + * and we can safely access its minimal fields. + */ + offs = p2n(a) - p2n(ctx->mrec); + space = le32_to_cpu(ctx->mrec->bytes_in_use) - offs; + if (offs < 0) break; - if (le32_eq(a->type, AT_END)) + if ((space >= 4) && le32_eq(a->type, AT_END)) continue; - if (le32_cmpz(a->length)) + if ((space < (ptrdiff_t)offsetof(ATTR_RECORD, resident_end)) + || (space < (ptrdiff_t)le32_to_cpu(a->length))) break; if (!le16_eq(al_entry->instance, a->instance)) goto do_next_attr; @@ -3241,13 +3330,15 @@ do_next_attr: a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length)); goto do_next_attr_loop; } +corrupt : if (ni != base_ni) { ctx->ntfs_ino = base_ni; ctx->mrec = ctx->base_mrec; ctx->attr = ctx->base_attr; } errno = EIO; - ntfs_log_perror("Inode is corrupt (%lld)", (long long)base_ni->mft_no); + ntfs_log_error("Corrupt attribute list entry in MFT record %lld\n", + (long long)base_ni->mft_no); return -1; not_found: /* @@ -3299,6 +3390,190 @@ not_found: } } +/* + * Check the consistency of an attribute + * + * Do the general consistency checks of the selected attribute : + * - the required fields can be accessed + * - the variable fields do not overflow + * - the attribute is [non-]resident if it must be + * - miscelleaneous checks + * + * Returns 0 if the checks pass + * -1 with errno = EIO otherwise + */ + +int ntfs_attr_inconsistent(const ATTR_RECORD *a, const MFT_REF mref) +{ + const FILE_NAME_ATTR *fn; + const INDEX_ROOT *ir; + u64 inum; + int ret; + + /* + * The attribute was found to fully lie within the MFT + * record, now make sure its relevant parts (name, runlist, + * value) also lie within. The first step is to make sure + * the attribute has the minimum length so that accesses to + * the lengths and offsets of these parts are safe. + */ + ret = 0; + inum = MREF(mref); + if (a->non_resident) { + if ((a->non_resident != 1) + || (le32_to_cpu(a->length) + < offsetof(ATTR_RECORD, non_resident_end)) + || (le16_to_cpu(a->mapping_pairs_offset) + >= le32_to_cpu(a->length)) + || (a->name_length + && (((u32)le16_to_cpu(a->name_offset) + + a->name_length * sizeof(ntfschar)) + > le32_to_cpu(a->length))) + || (le64_to_cpu(a->highest_vcn) + < le64_to_cpu(a->lowest_vcn))) { + ntfs_log_error("Corrupt non resident attribute" + " 0x%x in MFT record %lld\n", + (int)le32_to_cpu(a->type), + (long long)inum); + errno = EIO; + ret = -1; + } + } else { + if ((le32_to_cpu(a->length) + < offsetof(ATTR_RECORD, resident_end)) + || (le32_to_cpu(a->value_length) & 0xff000000) + || (!le32_cmpz(a->value_length) + && ((le16_to_cpu(a->value_offset) + + le32_to_cpu(a->value_length)) + > le32_to_cpu(a->length))) + || (a->name_length + && (((u32)le16_to_cpu(a->name_offset) + + a->name_length * sizeof(ntfschar)) + > le32_to_cpu(a->length)))) { + ntfs_log_error("Corrupt resident attribute" + " 0x%x in MFT record %lld\n", + (int)le32_to_cpu(a->type), + (long long)inum); + errno = EIO; + ret = -1; + } + } + if (!ret) { + /* + * Checking whether an attribute must be [non-]resident + * is hard-coded for well-known ones. This should be + * done through ntfs_attr_can_be_non_resident(), based on + * $AttrDef, but this would give an easy way to bypass + * the checks. + * Attributes which are not well-known are not checked. + * + * Note : at this stage we know that a->length and + * a->value_length cannot look like being negative. + */ + /* switch(a->type) { */ + if (le32_eq(a->type, AT_FILE_NAME)) { + /* Check file names are resident and do not overflow */ + fn = (const FILE_NAME_ATTR*)((const u8*)a + + le16_to_cpu(a->value_offset)); + if (a->non_resident + || (le32_to_cpu(a->value_length) + < offsetof(FILE_NAME_ATTR, file_name)) + || !fn->file_name_length + || ((fn->file_name_length * sizeof(ntfschar) + + offsetof(FILE_NAME_ATTR, file_name)) + > le32_to_cpu(a->value_length))) { + ntfs_log_error("Corrupt file name" + " attribute in MFT record %lld.\n", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_INDEX_ROOT)) { + /* Check root index is resident and does not overflow */ + ir = (const INDEX_ROOT*)((const u8*)a + + le16_to_cpu(a->value_offset)); + /* index.allocated_size may overflow while resizing */ + if (a->non_resident + || (le32_to_cpu(a->value_length) + < offsetof(INDEX_ROOT, index.reserved)) + || (le32_to_cpu(ir->index.entries_offset) + < sizeof(INDEX_HEADER)) + || (le32_to_cpu(ir->index.index_length) + < le32_to_cpu(ir->index.entries_offset)) + || (le32_to_cpu(ir->index.allocated_size) + < le32_to_cpu(ir->index.index_length)) + || (le32_to_cpu(a->value_length) + < (le32_to_cpu(ir->index.allocated_size) + + offsetof(INDEX_ROOT, reserved)))) { + ntfs_log_error("Corrupt index root" + " in MFT record %lld.\n", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_STANDARD_INFORMATION)) { + if (a->non_resident + || (le32_to_cpu(a->value_length) + < offsetof(STANDARD_INFORMATION, + v1_end))) { + ntfs_log_error("Corrupt standard information" + " in MFT record %lld\n", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_OBJECT_ID)) { + if (a->non_resident + || (le32_to_cpu(a->value_length) + < sizeof(GUID))) { + ntfs_log_error("Corrupt object id" + " in MFT record %lld\n", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_VOLUME_NAME) || + le32_eq(a->type, AT_EA_INFORMATION)) { + if (a->non_resident) { + ntfs_log_error("Attribute 0x%x in MFT record" + " %lld should be resident.\n", + (int)le32_to_cpu(a->type), + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_VOLUME_INFORMATION)) { + if (a->non_resident + || (le32_to_cpu(a->value_length) + < sizeof(VOLUME_INFORMATION))) { + ntfs_log_error("Corrupt volume information" + " in MFT record %lld\n", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else if (le32_eq(a->type, AT_INDEX_ALLOCATION)) { + if (!a->non_resident) { + ntfs_log_error("Corrupt index allocation" + " in MFT record %lld", + (long long)inum); + errno = EIO; + ret = -1; + } + } + else { + } + /* } */ + } + return (ret); +} + /** * ntfs_attr_lookup - find an attribute in an ntfs inode * @type: attribute type to find @@ -3546,8 +3821,9 @@ ATTR_DEF *ntfs_attr_find_in_attrdef(const ntfs_volume *vol, ntfs_log_perror("%s: type=%d", __FUNCTION__, le32_to_cpu(type)); return NULL; } - for (ad = vol->attrdef; (u8*)ad - (u8*)vol->attrdef < - vol->attrdef_len && !le32_cmpz(ad->type); ++ad) { + for (ad = vol->attrdef; ((ptrdiff_t)((u8*)ad - (u8*)vol->attrdef + + sizeof(ATTR_DEF)) <= vol->attrdef_len) + && !le32_cmpz(ad->type); ++ad) { /* We haven't found it yet, carry on searching. */ if (le32_to_cpu(ad->type) < le32_to_cpu(type)) continue; @@ -4533,6 +4809,13 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size) } /* Move attributes following @a to their new location. */ + if (((u8 *)m + old_size) < ((u8 *)a + attr_size)) { + ntfs_log_error("Attribute 0x%x overflows" + " from MFT record\n", + (int)le32_to_cpu(a->type)); + errno = EIO; + return (-1); + } memmove((u8 *)a + new_size, (u8 *)a + attr_size, old_size - ((u8 *)a - (u8 *)m) - attr_size); @@ -4567,6 +4850,13 @@ int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a, ntfs_log_trace("Entering for new size %u.\n", (unsigned)new_size); + if (le32_cmpz(a->value_length)) { + /* Offset is unsafe when no value */ + int offset = ((offsetof(ATTR_RECORD, resident_end) + + a->name_length*sizeof(ntfschar) - 1) | 7) + 1; + a->value_offset = cpu_to_le16(offset); + } + /* Resize the resident part of the attribute record. */ if ((ret = ntfs_attr_record_resize(m, a, (le16_to_cpu(a->value_offset) + new_size + 7) & ~7)) < 0) @@ -6663,6 +6953,20 @@ void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type, ntfs_log_perror("ntfs_attr_open failed, inode %lld attr 0x%lx", (long long)ni->mft_no,(long)le32_to_cpu(type)); goto err_exit; + } + /* + * Consistency check : restrict to 65536 bytes. + * index bitmaps may need more, but still limited by + * the number of clusters. + */ + if (((u64)na->data_size > 65536) + && (!le32_eq(type, AT_BITMAP) + || ((u64)na->data_size > + (u64)((ni->vol->nr_clusters + 7) >> 3)))) { + ntfs_log_error("Corrupt attribute 0x%lx in inode %lld\n", + (long)le32_to_cpu(type),(long long)ni->mft_no); + errno = EOVERFLOW; + goto out; } data = ntfs_malloc(na->data_size); if (!data) diff --git a/libntfs-3g/bootsect.c b/libntfs-3g/bootsect.c index 0bc56088..d4472ff2 100644 --- a/libntfs-3g/bootsect.c +++ b/libntfs-3g/bootsect.c @@ -155,7 +155,9 @@ BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b) } /* MFT and MFTMirr may not overlap the boot sector or be the same */ - if (sle64_cmpz(b->mft_lcn) || sle64_cmpz(b->mftmirr_lcn) || sle64_eq(b->mft_lcn, b->mftmirr_lcn)) { + if (((s64)sle64_to_cpu(b->mft_lcn) <= 0) + || ((s64)sle64_to_cpu(b->mftmirr_lcn) <= 0) + || sle64_eq(b->mft_lcn, b->mftmirr_lcn)) { ntfs_log_error("Invalid location of MFT or MFTMirr.\n"); goto not_ntfs; } diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index 85b2e64e..c45a7d94 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -752,6 +752,12 @@ s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, void *b) /* If it is a resident attribute, simply use ntfs_attr_pread(). */ if (!NAttrNonResident(na)) return ntfs_attr_pread(na, pos, count, b); + if (na->compression_block_size < NTFS_SB_SIZE) { + ntfs_log_error("Unsupported compression block size %ld\n", + (long)na->compression_block_size); + errno = EOVERFLOW; + return (-1); + } total = total2 = 0; /* Zero out reads beyond initialized size. */ if (pos + count > na->initialized_size) { @@ -1618,7 +1624,7 @@ static int ntfs_read_append(ntfs_attr *na, const runlist_element *rl, */ static s32 ntfs_flush(ntfs_attr *na, runlist_element *rl, s64 offs, - const char *outbuf, s32 count, BOOL compress, + char *outbuf, s32 count, BOOL compress, BOOL appending, VCN *update_from) { s32 rounded; @@ -1639,6 +1645,8 @@ static s32 ntfs_flush(ntfs_attr *na, runlist_element *rl, s64 offs, if (!compress) { clsz = 1 << na->ni->vol->cluster_size_bits; rounded = ((count - 1) | (clsz - 1)) + 1; + if (rounded > count) + memset(&outbuf[count], 0, rounded - count); written = write_clusters(na->ni->vol, rl, offs, rounded, outbuf); if (written != rounded) @@ -1700,6 +1708,12 @@ s64 ntfs_compressed_pwrite(ntfs_attr *na, runlist_element *wrl, s64 wpos, errno = EIO; return (-1); } + if (na->compression_block_size < NTFS_SB_SIZE) { + ntfs_log_error("Unsupported compression block size %ld\n", + (long)na->compression_block_size); + errno = EOVERFLOW; + return (-1); + } if (wrl->vcn < *update_from) *update_from = wrl->vcn; written = -1; /* default return */ @@ -1881,6 +1895,12 @@ int ntfs_compressed_close(ntfs_attr *na, runlist_element *wrl, s64 offs, errno = EIO; return (-1); } + if (na->compression_block_size < NTFS_SB_SIZE) { + ntfs_log_error("Unsupported compression block size %ld\n", + (long)na->compression_block_size); + errno = EOVERFLOW; + return (-1); + } if (wrl->vcn < *update_from) *update_from = wrl->vcn; vol = na->ni->vol; diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 7f4d438a..1ce58be7 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -293,6 +293,7 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, (unsigned)index_block_size); goto put_err_out; } + /* Consistency check of ir done while fetching attribute */ index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); /* The first index entry. */ ie = (INDEX_ENTRY*)((u8*)&ir->index + @@ -305,10 +306,11 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, /* Bounds checks. */ if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->key_length) > + (u8*)ie + le16_to_cpu(ie->length) > index_end) { - ntfs_log_error("Index entry out of bounds in inode %lld" - "\n", (unsigned long long)dir_ni->mft_no); + ntfs_log_error("Index root entry out of bounds in" + " inode %lld\n", + (unsigned long long)dir_ni->mft_no); goto put_err_out; } /* @@ -318,9 +320,10 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, if (!le16_andz(ie->ie_flags, INDEX_ENTRY_END)) break; - if (!le16_to_cpu(ie->length)) { - ntfs_log_error("Zero length index entry in inode %lld" - "\n", (unsigned long long)dir_ni->mft_no); + /* The file name must not overflow from the entry */ + if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) { + errno = EIO; goto put_err_out; } /* @@ -398,37 +401,18 @@ descend_into_child_node: if (br != 1) { if (br != -1) errno = EIO; - ntfs_log_perror("Failed to read vcn 0x%llx", - (unsigned long long)vcn); + ntfs_log_perror("Failed to read vcn 0x%llx from inode %lld", + (unsigned long long)vcn, + (unsigned long long)ia_na->ni->mft_no); goto close_err_out; } - if (sle64_to_cpu(ia->index_block_vcn) != vcn) { - ntfs_log_error("Actual VCN (0x%llx) of index buffer is different " - "from expected VCN (0x%llx).\n", - (long long)sle64_to_cpu(ia->index_block_vcn), - (long long)vcn); - errno = EIO; - goto close_err_out; - } - if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { - ntfs_log_error("Index buffer (VCN 0x%llx) of directory inode 0x%llx " - "has a size (%u) differing from the directory " - "specified size (%u).\n", (long long)vcn, - (unsigned long long)dir_ni->mft_no, - (unsigned) le32_to_cpu(ia->index.allocated_size) + 0x18, - (unsigned)index_block_size); + if (ntfs_index_block_inconsistent((INDEX_BLOCK*)ia, index_block_size, + ia_na->ni->mft_no, vcn)) { errno = EIO; goto close_err_out; } index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); - if (index_end > (u8*)ia + index_block_size) { - ntfs_log_error("Size of index buffer (VCN 0x%llx) of directory inode " - "0x%llx exceeds maximum size.\n", - (long long)vcn, (unsigned long long)dir_ni->mft_no); - errno = EIO; - goto close_err_out; - } /* The first index entry. */ ie = (INDEX_ENTRY*)((u8*)&ia->index + @@ -442,7 +426,7 @@ descend_into_child_node: /* Bounds check. */ if ((u8*)ie < (u8*)ia || (u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->key_length) > + (u8*)ie + le16_to_cpu(ie->length) > index_end) { ntfs_log_error("Index entry out of bounds in directory " "inode %lld.\n", @@ -457,10 +441,10 @@ descend_into_child_node: if (!le16_andz(ie->ie_flags, INDEX_ENTRY_END)) break; - if (!le16_to_cpu(ie->length)) { + /* The file name must not overflow from the entry */ + if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) { errno = EIO; - ntfs_log_error("Zero length index entry in inode %lld" - "\n", (unsigned long long)dir_ni->mft_no); goto close_err_out; } /* @@ -1086,12 +1070,6 @@ static MFT_REF ntfs_mft_get_parent_ref(ntfs_inode *ni) } fn = (FILE_NAME_ATTR*)((u8*)ctx->attr + le16_to_cpu(ctx->attr->value_offset)); - if ((u8*)fn + le32_to_cpu(ctx->attr->value_length) > - (u8*)ctx->attr + le32_to_cpu(ctx->attr->length)) { - ntfs_log_error("Corrupt file name attribute in inode %lld.\n", - (unsigned long long)ni->mft_no); - goto io_err_out; - } mref = le64_to_cpu(fn->parent_directory); ntfs_attr_put_search_ctx(ctx); return mref; @@ -1250,9 +1228,13 @@ int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, /* Bounds checks. */ if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->key_length) > - index_end) + (u8*)ie + le16_to_cpu(ie->length) > + index_end) { + ntfs_log_error("Index root entry out of bounds in" + " inode %lld\n", + (unsigned long long)dir_ni->mft_no); goto dir_err_out; + } /* The last entry cannot contain a name. */ if (!le16_andz(ie->ie_flags, INDEX_ENTRY_END)) break; @@ -1263,6 +1245,13 @@ int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, /* Skip index root entry if continuing previous readdir. */ if (ir_pos > (u8*)ie - (u8*)ir) continue; + + /* The file name must not overflow from the entry */ + if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) { + errno = EIO; + goto dir_err_out; + } /* * Submit the directory entry to ntfs_filldir(), which will * invoke the filldir() callback as appropriate. @@ -1362,33 +1351,12 @@ find_next_index_buffer: } ia_start = ia_pos & ~(s64)(index_block_size - 1); - if (sle64_to_cpu(ia->index_block_vcn) != ia_start >> - index_vcn_size_bits) { - ntfs_log_error("Actual VCN (0x%llx) of index buffer is different " - "from expected VCN (0x%llx) in inode 0x%llx.\n", - (long long)sle64_to_cpu(ia->index_block_vcn), - (long long)ia_start >> index_vcn_size_bits, - (unsigned long long)dir_ni->mft_no); - goto dir_err_out; - } - if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { - ntfs_log_error("Index buffer (VCN 0x%llx) of directory inode %lld " - "has a size (%u) differing from the directory " - "specified size (%u).\n", (long long)ia_start >> - index_vcn_size_bits, - (unsigned long long)dir_ni->mft_no, - (unsigned) le32_to_cpu(ia->index.allocated_size) - + 0x18, (unsigned)index_block_size); + if (ntfs_index_block_inconsistent((INDEX_BLOCK*)ia, index_block_size, + ia_na->ni->mft_no, ia_start >> index_vcn_size_bits)) { goto dir_err_out; } index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); - if (index_end > (u8*)ia + index_block_size) { - ntfs_log_error("Size of index buffer (VCN 0x%llx) of directory inode " - "%lld exceeds maximum size.\n", - (long long)ia_start >> index_vcn_size_bits, - (unsigned long long)dir_ni->mft_no); - goto dir_err_out; - } + /* The first index entry. */ ie = (INDEX_ENTRY*)((u8*)&ia->index + le32_to_cpu(ia->index.entries_offset)); @@ -1403,7 +1371,7 @@ find_next_index_buffer: /* Bounds checks. */ if ((u8*)ie < (u8*)ia || (u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->key_length) > + (u8*)ie + le16_to_cpu(ie->length) > index_end) { ntfs_log_error("Index entry out of bounds in directory inode " "%lld.\n", (unsigned long long)dir_ni->mft_no); @@ -1419,6 +1387,13 @@ find_next_index_buffer: /* Skip index entry if continuing previous readdir. */ if (ia_pos - ia_start > (u8*)ie - (u8*)ia) continue; + + /* The file name must not overflow from the entry */ + if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) { + errno = EIO; + goto dir_err_out; + } /* * Submit the directory entry to ntfs_filldir(), which will * invoke the filldir() callback as appropriate. diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index 7db395a0..04b681ff 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2005-2006 Yura Pakhuchiy * Copyright (c) 2005-2008 Szabolcs Szakacsits - * Copyright (c) 2007-2020 Jean-Pierre Andre + * Copyright (c) 2007-2021 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 @@ -388,42 +388,6 @@ static INDEX_ENTRY *ntfs_ie_dup_novcn(INDEX_ENTRY *ie) return dup; } -static int ntfs_ia_check(ntfs_index_context *icx, INDEX_BLOCK *ib, VCN vcn) -{ - u32 ib_size = (unsigned)le32_to_cpu(ib->index.allocated_size) + 0x18; - - ntfs_log_trace("Entering\n"); - - if (!ntfs_is_indx_record(ib->magic)) { - - ntfs_log_error("Corrupt index block signature: vcn %lld inode " - "%llu\n", (long long)vcn, - (unsigned long long)icx->ni->mft_no); - return -1; - } - - if (sle64_to_cpu(ib->index_block_vcn) != vcn) { - - ntfs_log_error("Corrupt index block: VCN (%lld) is different " - "from expected VCN (%lld) in inode %llu\n", - (long long)sle64_to_cpu(ib->index_block_vcn), - (long long)vcn, - (unsigned long long)icx->ni->mft_no); - return -1; - } - - if (ib_size != icx->block_size) { - - ntfs_log_error("Corrupt index block : VCN (%lld) of inode %llu " - "has a size (%u) differing from the index " - "specified size (%u)\n", (long long)vcn, - (unsigned long long)icx->ni->mft_no, ib_size, - icx->block_size); - return -1; - } - return 0; -} - static INDEX_ROOT *ntfs_ir_lookup(ntfs_inode *ni, ntfschar *name, u32 name_len, ntfs_attr_search_ctx **ctx) { @@ -469,6 +433,133 @@ static INDEX_ROOT *ntfs_ir_lookup2(ntfs_inode *ni, ntfschar *name, u32 len) return ir; } +/* + * Check the consistency of an index block + * + * Make sure the index block does not overflow from the index record. + * The size of block is assumed to have been checked to be what is + * defined in the index root. + * + * Returns 0 if no error was found + * -1 otherwise (with errno unchanged) + * + * |<--->| offsetof(INDEX_BLOCK, index) + * | |<--->| sizeof(INDEX_HEADER) + * | | | + * | | | seq index entries unused + * |=====|=====|=====|===========================|==============| + * | | | | | + * | |<--------->| entries_offset | | + * | |<---------------- index_length ------->| | + * | |<--------------------- allocated_size --------------->| + * |<--------------------------- block_size ------------------->| + * + * size(INDEX_HEADER) <= ent_offset < ind_length <= alloc_size < bk_size + */ + +int ntfs_index_block_inconsistent(const INDEX_BLOCK *ib, u32 block_size, + u64 inum, VCN vcn) +{ + u32 ib_size = (unsigned)le32_to_cpu(ib->index.allocated_size) + + offsetof(INDEX_BLOCK, index); + + if (!ntfs_is_indx_record(ib->magic)) { + ntfs_log_error("Corrupt index block signature: vcn %lld inode " + "%llu\n", (long long)vcn, + (unsigned long long)inum); + return -1; + } + + if (sle64_to_cpu(ib->index_block_vcn) != vcn) { + ntfs_log_error("Corrupt index block: VCN (%lld) is different " + "from expected VCN (%lld) in inode %llu\n", + (long long)sle64_to_cpu(ib->index_block_vcn), + (long long)vcn, + (unsigned long long)inum); + return -1; + } + + if (ib_size != block_size) { + ntfs_log_error("Corrupt index block : VCN (%lld) of inode %llu " + "has a size (%u) differing from the index " + "specified size (%u)\n", (long long)vcn, + (unsigned long long)inum, ib_size, + (unsigned int)block_size); + return -1; + } + if (le32_to_cpu(ib->index.entries_offset) < sizeof(INDEX_HEADER)) { + ntfs_log_error("Invalid index entry offset in inode %lld\n", + (unsigned long long)inum); + return -1; + } + if (le32_to_cpu(ib->index.index_length) + <= le32_to_cpu(ib->index.entries_offset)) { + ntfs_log_error("No space for index entries in inode %lld\n", + (unsigned long long)inum); + return -1; + } + if (le32_to_cpu(ib->index.allocated_size) + < le32_to_cpu(ib->index.index_length)) { + ntfs_log_error("Index entries overflow in inode %lld\n", + (unsigned long long)inum); + return -1; + } + + return (0); +} + + +/* + * Check the consistency of an index entry + * + * Make sure data and key do not overflow from entry. + * As a side effect, an entry with zero length is rejected. + * This entry must be a full one (no INDEX_ENTRY_END flag), and its + * length must have been checked beforehand to not overflow from the + * index record. + * + * Returns 0 if no error was found + * -1 otherwise (with errno unchanged) + */ + +int ntfs_index_entry_inconsistent(const INDEX_ENTRY *ie, + COLLATION_RULES collation_rule, u64 inum) +{ + int ret; + + ret = 0; + if (!le16_cmpz(ie->key_length) + && ((le16_to_cpu(ie->key_length) + offsetof(INDEX_ENTRY, key)) + > le16_to_cpu(ie->length))) { + ntfs_log_error("Overflow from index entry in inode %lld\n", + (long long)inum); + ret = -1; + + } else + if (le32_eq(collation_rule, COLLATION_FILE_NAME)) { + if ((offsetof(INDEX_ENTRY, key.file_name.file_name) + + ie->key.file_name.file_name_length + * sizeof(ntfschar)) + > le16_to_cpu(ie->length)) { + ntfs_log_error("File name overflow from index" + " entry in inode %lld\n", + (long long)inum); + ret = -1; + } + } else { + if (!le16_cmpz(ie->data_length) + && ((le16_to_cpu(ie->data_offset) + + le16_to_cpu(ie->data_length)) + > le16_to_cpu(ie->length))) { + ntfs_log_error("Data overflow from index" + " entry in inode %lld\n", + (long long)inum); + ret = -1; + } + } + return (ret); +} + /** * Find a key in the index block. * @@ -521,6 +612,12 @@ static int ntfs_ie_lookup(const void *key, const int key_len, ntfs_log_error("Collation function not defined\n"); errno = EOPNOTSUPP; return STATUS_ERROR; + } + /* Make sure key and data do not overflow from entry */ + if (ntfs_index_entry_inconsistent(ie, icx->ir->collation_rule, + icx->ni->mft_no)) { + errno = EIO; + return STATUS_ERROR; } rc = icx->collate(icx->ni->vol, key, key_len, &ie->key, le16_to_cpu(ie->key_length)); @@ -606,8 +703,11 @@ static int ntfs_ib_read(ntfs_index_context *icx, VCN vcn, INDEX_BLOCK *dst) return -1; } - if (ntfs_ia_check(icx, dst, vcn)) + if (ntfs_index_block_inconsistent((INDEX_BLOCK*)dst, icx->block_size, + icx->ia_na->ni->mft_no, vcn)) { + errno = EIO; return -1; + } return 0; } @@ -703,6 +803,7 @@ int ntfs_index_lookup(const void *key, const int key_len, ntfs_index_context *ic else icx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS; /* get the appropriate collation function */ + icx->ir = ir; icx->collate = ntfs_get_collate_function(ir->collation_rule); if (!icx->collate) { err = errno = EOPNOTSUPP; @@ -712,10 +813,6 @@ int ntfs_index_lookup(const void *key, const int key_len, ntfs_index_context *ic } old_vcn = VCN_INDEX_ROOT_PARENT; - /* - * FIXME: check for both ir and ib that the first index entry is - * within the index block. - */ ret = ntfs_ie_lookup(key, key_len, icx, &ir->index, &vcn, &ie); if (ret == STATUS_ERROR) { err = errno; @@ -782,6 +879,10 @@ descend_into_child_node: err_out: icx->bad_index = TRUE; /* Force icx->* to be freed */ err_lookup: + if (icx->actx) { + ntfs_attr_put_search_ctx(icx->actx); + icx->actx = NULL; + } free(ib); if (!err) err = EIO; diff --git a/libntfs-3g/inode.c b/libntfs-3g/inode.c index 6f853740..9c1dd8aa 100644 --- a/libntfs-3g/inode.c +++ b/libntfs-3g/inode.c @@ -189,6 +189,13 @@ static ntfs_inode *ntfs_inode_real_open(ntfs_volume *vol, const MFT_REF mref) " %lld", (long long)MREF(mref)); goto put_err_out; } + lthle = ctx->attr->value_length; + if (le32_to_cpu(lthle) < offsetof(STANDARD_INFORMATION, owner_id)) { + ntfs_log_error("Corrupt STANDARD_INFORMATION in base" + " record %lld\n", + (long long)MREF(mref)); + goto put_err_out; + } std_info = (STANDARD_INFORMATION *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->value_offset)); ni->flags = std_info->file_attributes; @@ -196,10 +203,9 @@ static ntfs_inode *ntfs_inode_real_open(ntfs_volume *vol, const MFT_REF mref) ni->last_data_change_time = std_info->last_data_change_time; ni->last_mft_change_time = std_info->last_mft_change_time; ni->last_access_time = std_info->last_access_time; - /* JPA insert v3 extensions if present */ - /* length may be seen as 72 (v1.x) or 96 (v3.x) */ - lthle = ctx->attr->length; - if (le32_to_cpu(lthle) > sizeof(STANDARD_INFORMATION)) { + /* Insert v3 extensions if present */ + /* length may be seen as 48 (v1.x) or 72 (v3.x) */ + if (le32_to_cpu(lthle) >= offsetof(STANDARD_INFORMATION, v3_end)) { set_nino_flag(ni, v3_Extensions); ni->owner_id = std_info->owner_id; ni->security_id = std_info->security_id; @@ -225,9 +231,9 @@ static ntfs_inode *ntfs_inode_real_open(ntfs_volume *vol, const MFT_REF mref) l = ntfs_get_attribute_value_length(ctx->attr); if (!l) goto put_err_out; - if (l > 0x40000) { + if ((u64)l > 0x40000) { errno = EIO; - ntfs_log_perror("Too large attrlist attribute (%lld), inode " + ntfs_log_perror("Too large attrlist attribute (%llu), inode " "%lld", (long long)l, (long long)MREF(mref)); goto put_err_out; } @@ -760,13 +766,13 @@ static int ntfs_inode_sync_standard_information(ntfs_inode *ni) /* JPA update v3.x extensions, ensuring consistency */ - lthle = ctx->attr->length; + lthle = ctx->attr->value_length; lth = le32_to_cpu(lthle); if (test_nino_flag(ni, v3_Extensions) - && (lth <= sizeof(STANDARD_INFORMATION))) + && (lth < offsetof(STANDARD_INFORMATION, v3_end))) ntfs_log_error("bad sync of standard information\n"); - if (lth > sizeof(STANDARD_INFORMATION)) { + if (lth >= offsetof(STANDARD_INFORMATION, v3_end)) { std_info->owner_id = ni->owner_id; std_info->security_id = ni->security_id; std_info->quota_charged = ni->quota_charged; diff --git a/libntfs-3g/lcnalloc.c b/libntfs-3g/lcnalloc.c index 486e3510..a1c1ee33 100644 --- a/libntfs-3g/lcnalloc.c +++ b/libntfs-3g/lcnalloc.c @@ -368,12 +368,14 @@ runlist *ntfs_cluster_alloc(ntfs_volume *vol, VCN start_vcn, s64 count, /* Allocate the bitmap bit. */ *byte |= bit; writeback = 1; - if (vol->free_clusters <= 0) - ntfs_log_error("Non-positive free clusters " - "(%lld)!\n", + if (NVolFreeSpaceKnown(vol)) { + if (vol->free_clusters <= 0) + ntfs_log_error("Non-positive free" + " clusters (%lld)!\n", (long long)vol->free_clusters); - else - vol->free_clusters--; + else + vol->free_clusters--; + } /* * Coalesce with previous run if adjacent LCNs. @@ -602,7 +604,8 @@ int ntfs_cluster_free_from_rl(ntfs_volume *vol, runlist *rl) ret = 0; out: vol->free_clusters += nr_freed; - if (vol->free_clusters > vol->nr_clusters) + if (NVolFreeSpaceKnown(vol) + && (vol->free_clusters > vol->nr_clusters)) ntfs_log_error("Too many free clusters (%lld > %lld)!", (long long)vol->free_clusters, (long long)vol->nr_clusters); diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c index 9d86acb2..6c82e5b2 100644 --- a/libntfs-3g/mft.c +++ b/libntfs-3g/mft.c @@ -172,6 +172,15 @@ int ntfs_mft_records_write(const ntfs_volume *vol, const MFT_REF mref, cnt = vol->mftmirr_size - m; if (cnt > count) cnt = count; + if ((m + cnt) > vol->mftmirr_na->initialized_size >> + vol->mft_record_size_bits) { + errno = ESPIPE; + ntfs_log_perror("Trying to write non-allocated mftmirr" + " records (%lld > %lld)", (long long)m + cnt, + (long long)vol->mftmirr_na->initialized_size >> + vol->mft_record_size_bits); + return -1; + } bmirr = ntfs_malloc(cnt * vol->mft_record_size); if (!bmirr) return -1; @@ -210,11 +219,26 @@ int ntfs_mft_records_write(const ntfs_volume *vol, const MFT_REF mref, return -1; } +/* + * Check the consistency of an MFT record + * + * Make sure its general fields are safe, then examine all its + * attributes and apply generic checks to them. + * The attribute checks are skipped when a record is being read in + * order to collect its sequence number for creating a new record. + * + * Returns 0 if the checks are successful + * -1 with errno = EIO otherwise + */ + int ntfs_mft_record_check(const ntfs_volume *vol, const MFT_REF mref, MFT_RECORD *m) { ATTR_RECORD *a; + ATTR_TYPES previous_type; int ret = -1; + u32 offset; + s32 space; if (!ntfs_is_file_record(m->magic)) { if (!NVolNoFixupWarn(vol)) @@ -231,13 +255,57 @@ int ntfs_mft_record_check(const ntfs_volume *vol, const MFT_REF mref, le32_to_cpu(m->bytes_allocated)); goto err_out; } - + if (!NVolNoFixupWarn(vol) + && (le32_to_cpu(m->bytes_in_use) > vol->mft_record_size)) { + ntfs_log_error("Record %llu has corrupt in-use size " + "(%u > %u)\n", (unsigned long long)MREF(mref), + (int)le32_to_cpu(m->bytes_in_use), + (int)vol->mft_record_size); + goto err_out; + } + if (le16_to_cpu(m->attrs_offset) & 7) { + ntfs_log_error("Attributes badly aligned in record %llu\n", + (unsigned long long)MREF(mref)); + goto err_out; + } + a = (ATTR_RECORD *)((char *)m + le16_to_cpu(m->attrs_offset)); if (p2n(a) < p2n(m) || (char *)a > (char *)m + vol->mft_record_size) { ntfs_log_error("Record %llu is corrupt\n", (unsigned long long)MREF(mref)); goto err_out; } + + if (!NVolNoFixupWarn(vol)) { + offset = le16_to_cpu(m->attrs_offset); + space = le32_to_cpu(m->bytes_in_use) - offset; + a = (ATTR_RECORD*)((char*)m + offset); + previous_type = AT_STANDARD_INFORMATION; + while ((space >= (s32)offsetof(ATTR_RECORD, resident_end)) + && !le32_eq(a->type, AT_END) + && (le32_to_cpu(a->type) >= le32_to_cpu(previous_type))) { + if ((le32_to_cpu(a->length) <= (u32)space) + && !(le32_to_cpu(a->length) & 7)) { + if (!ntfs_attr_inconsistent(a, mref)) { + previous_type = a->type; + offset += le32_to_cpu(a->length); + space -= le32_to_cpu(a->length); + a = (ATTR_RECORD*)((char*)m + offset); + } else + goto err_out; + } else { + ntfs_log_error("Corrupted MFT record %llu\n", + (unsigned long long)MREF(mref)); + goto err_out; + } + } + /* We are supposed to reach an AT_END */ + if ((space < 4) || !le32_eq(a->type, AT_END)) { + ntfs_log_error("Bad end of MFT record %llu\n", + (unsigned long long)MREF(mref)); + goto err_out; + } + } ret = 0; err_out: diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c index f1b1b012..f05514c6 100644 --- a/libntfs-3g/volume.c +++ b/libntfs-3g/volume.c @@ -221,15 +221,24 @@ static int __ntfs_volume_release(ntfs_volume *v) return errno ? -1 : 0; } -static void ntfs_attr_setup_flag(ntfs_inode *ni) +static int ntfs_attr_setup_flag(ntfs_inode *ni) { STANDARD_INFORMATION *si; + s64 lth; + int r; - si = ntfs_attr_readall(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, NULL); + si = (STANDARD_INFORMATION*)ntfs_attr_readall(ni, + AT_STANDARD_INFORMATION, AT_UNNAMED, 0, <h); if (si) { - ni->flags = si->file_attributes; + if ((u64)lth >= offsetof(STANDARD_INFORMATION, owner_id)) + ni->flags = si->file_attributes; free(si); + r = 0; + } else { + ntfs_log_error("Failed to get standard information of $MFT\n"); + r = -1; } + return (r); } /** @@ -303,16 +312,19 @@ static int ntfs_mft_load(ntfs_volume *vol) ntfs_log_error("Failed to get value of $MFT/$ATTR_LIST.\n"); goto io_error_exit; } - if (l != vol->mft_ni->attr_list_size) { + if ((l != vol->mft_ni->attr_list_size) + || (l < (s64)offsetof(ATTR_LIST_ENTRY, name))) { ntfs_log_error("Partial read of $MFT/$ATTR_LIST (%lld != " - "%u).\n", (long long)l, - vol->mft_ni->attr_list_size); + "%u or < %d).\n", (long long)l, + vol->mft_ni->attr_list_size, + (int)offsetof(ATTR_LIST_ENTRY, name)); goto io_error_exit; } mft_has_no_attr_list: - ntfs_attr_setup_flag(vol->mft_ni); + if (ntfs_attr_setup_flag(vol->mft_ni)) + goto error_exit; /* We now have a fully setup ntfs inode for $MFT in vol->mft_ni. */ @@ -355,6 +367,11 @@ mft_has_no_attr_list: ntfs_log_perror("ntfs_mapping_pairs_decompress() failed"); goto error_exit; } + /* Make sure $DATA is the MFT itself */ + if (nrl->lcn != vol->mft_lcn) { + ntfs_log_perror("The MFT is not self-contained"); + goto error_exit; + } vol->mft_na->rl = nrl; /* Get the lowest vcn for the next extent. */ @@ -448,6 +465,12 @@ static int ntfs_mftmirr_load(ntfs_volume *vol) ntfs_log_perror("Failed to map runlist of $MFTMirr/$DATA"); goto error_exit; } + if (vol->mftmirr_na->rl->lcn != vol->mftmirr_lcn) { + ntfs_log_error("Bad $MFTMirr lcn 0x%llx, want 0x%llx\n", + (long long)vol->mftmirr_na->rl->lcn, + (long long)vol->mftmirr_lcn); + goto error_exit; + } return 0; @@ -601,6 +624,10 @@ ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev, vol->mft_zone_end = vol->mft_lcn + mft_zone_size; while (vol->mft_zone_end >= vol->nr_clusters) { mft_zone_size >>= 1; + if (!mft_zone_size) { + errno = EINVAL; + goto error_exit; + } vol->mft_zone_end = vol->mft_lcn + mft_zone_size; } ntfs_log_debug("mft_zone_end = 0x%llx\n", (long long)vol->mft_zone_end); @@ -949,6 +976,10 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) } goto error_exit; } + for (i = 0; (i < l) && (i < FILE_first_user); ++i) + if (ntfs_mft_record_check(vol, FILE_MFT + i, + (MFT_RECORD*)(m + i*vol->mft_record_size))) + goto error_exit; l = ntfs_attr_mst_pread(vol->mftmirr_na, 0, vol->mftmirr_size, vol->mft_record_size, m2); if (l != vol->mftmirr_size) { @@ -958,6 +989,10 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) } vol->mftmirr_size = l; } + for (i = 0; (i < l) && (i < FILE_first_user); ++i) + if (ntfs_mft_record_check(vol, FILE_MFT + i, + (MFT_RECORD*)(m2 + i*vol->mft_record_size))) + goto error_exit; ntfs_log_debug("Comparing $MFTMirr to $MFT...\n"); /* Windows 10 does not update the full $MFTMirr any more */ for (i = 0; (i < vol->mftmirr_size) && (i < FILE_first_user); ++i) { @@ -1048,19 +1083,19 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); if (!na) { ntfs_log_perror("Failed to open ntfs attribute"); + ntfs_inode_close(ni); goto error_exit; } /* * Note: Normally, the upcase table has a length equal to 65536 - * 2-byte Unicode characters but allow for different cases, so no - * checks done. Just check we don't overflow 32-bits worth of Unicode - * characters. + * 2-byte Unicode characters. Anyway we currently can only process + * such characters. */ - if (na->data_size & ~0x1ffffffffULL) { - ntfs_log_error("Error: Upcase table is too big (max 32-bit " - "allowed).\n"); + if ((na->data_size - 2) & ~0x1fffeULL) { + ntfs_log_error("Error: Upcase table is invalid (want size even " + "<= 131072).\n"); errno = EINVAL; - goto error_exit; + goto bad_upcase; } if (vol->upcase_len != na->data_size >> 1) { vol->upcase_len = na->data_size >> 1; @@ -1068,7 +1103,7 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) free(vol->upcase); vol->upcase = ntfs_malloc(na->data_size); if (!vol->upcase) - goto error_exit; + goto bad_upcase; } /* Read in the $DATA attribute value into the buffer. */ l = ntfs_attr_pread(na, 0, na->data_size, vol->upcase); @@ -1077,7 +1112,7 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) "(%lld != %lld).\n", (long long)l, (long long)na->data_size); errno = EIO; - goto error_exit; + goto bad_upcase; } /* Done with the $UpCase mft record. */ ntfs_attr_close(na); @@ -1213,10 +1248,10 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) ntfs_log_perror("Failed to open ntfs attribute"); goto error_exit; } - /* Check we don't overflow 32-bits. */ - if (na->data_size > 0xffffffffLL) { + /* Check we don't overflow 24-bits. */ + if ((u64)na->data_size > 0xffffffLL) { ntfs_log_error("Attribute definition table is too big (max " - "32-bit allowed).\n"); + "24-bit allowed).\n"); errno = EINVAL; goto error_exit; } @@ -1282,6 +1317,10 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) } return vol; +bad_upcase : + ntfs_attr_close(na); + ntfs_inode_close(ni); + goto error_exit; io_error_exit: errno = EIO; error_exit: @@ -1855,8 +1894,10 @@ int ntfs_volume_get_free_space(ntfs_volume *vol) if (vol->free_mft_records < 0) ntfs_log_perror("Failed to calculate free MFT records"); - else + else { + NVolSetFreeSpaceKnown(vol); ret = 0; + } } return (ret); } diff --git a/ntfsprogs/ntfscp.c b/ntfsprogs/ntfscp.c index 12fdb39d..5850f10b 100644 --- a/ntfsprogs/ntfscp.c +++ b/ntfsprogs/ntfscp.c @@ -1155,7 +1155,7 @@ close_attr: if (!fstat(fileno(in),&st)) { s64 change_time = st.st_mtime*10000000LL + NTFS_TIME_OFFSET; - out->last_data_change_time = cpu_to_le64(change_time); + out->last_data_change_time = cpu_to_sle64(change_time); ntfs_inode_update_times(out, 0); } else { ntfs_log_error("Failed to get the time stamp.\n"); diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index b23a8e16..02c0015e 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -780,14 +780,19 @@ static ATTR_RECORD *find_unnamed_attr(MFT_RECORD *mrec, ATTR_TYPES type) { ATTR_RECORD *a; u32 offset; + s32 space; /* fetch the requested attribute */ offset = le16_to_cpu(mrec->attrs_offset); + space = le32_to_cpu(mrec->bytes_in_use) - offset; a = (ATTR_RECORD*)((char*)mrec + offset); - while ((offset < le32_to_cpu(mrec->bytes_in_use)) + while ((space >= (s32)offsetof(ATTR_RECORD, resident_end)) && !le32_eq(a->type, AT_END) + && (le32_to_cpu(a->length) <= (u32)space) + && !(le32_to_cpu(a->length) & 7) && (!le32_eq(a->type, type) || a->name_length)) { offset += le32_to_cpu(a->length); + space -= le32_to_cpu(a->length); a = (ATTR_RECORD*)((char*)mrec + offset); } if ((offset >= le32_to_cpu(mrec->bytes_in_use)) @@ -823,7 +828,8 @@ static BOOL short_mft_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) vol->mft_record_size, mft0) == vol->mft_record_size) && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft0, - vol->mft_record_size)) { + vol->mft_record_size) + && !ntfs_mft_record_check(vol, 0, mft0)) { a = find_unnamed_attr(mft0,AT_DATA); if (a && a->non_resident @@ -961,7 +967,9 @@ static BOOL self_mapped_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) if ((ntfs_pread(vol->dev, offs, vol->mft_record_size, mft1) == vol->mft_record_size) && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft1, - vol->mft_record_size)) { + vol->mft_record_size) + && !ntfs_mft_record_check(vol, inum, mft1)) { + lowest_vcn = (SELFLOC_LIMIT*vol->mft_record_size) >> vol->cluster_size_bits; a = find_unnamed_attr(mft1,AT_DATA); @@ -1017,7 +1025,8 @@ static BOOL spare_record_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) if ((ntfs_pread(vol->dev, offs, vol->mft_record_size, mft2) == vol->mft_record_size) && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft2, - vol->mft_record_size)) { + vol->mft_record_size) + && !ntfs_mft_record_check(vol, inum, mft2)) { if (le64_cmpz(mft2->base_mft_record) && !le16_andz(mft2->flags, MFT_RECORD_IN_USE) && !find_unnamed_attr(mft2,AT_ATTRIBUTE_LIST) diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index b0ac8a37..6b1a7b35 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -263,7 +263,7 @@ static const char *usage_msg = "Copyright (C) 2005-2007 Yura Pakhuchiy\n" "Copyright (C) 2006-2009 Szabolcs Szakacsits\n" "Copyright (C) 2007-2021 Jean-Pierre Andre\n" -"Copyright (C) 2009 Erik Larsson\n" +"Copyright (C) 2009-2020 Erik Larsson\n" "\n" "Usage: %s [-o option[,...]] \n" "\n" @@ -4347,8 +4347,7 @@ static int ntfs_open(const char *device) if (ctx->ignore_case && ntfs_set_ignore_case(vol)) goto err_out; - vol->free_clusters = ntfs_attr_get_free_bits(vol->lcnbmp_na); - if (vol->free_clusters < 0) { + if (ntfs_volume_get_free_space(ctx->vol)) { ntfs_log_perror("Failed to read NTFS $Bitmap"); goto err_out; } @@ -4368,9 +4367,12 @@ static int ntfs_open(const char *device) } errno = 0; + goto out; err_out: + if (!errno) /* Make sure to return an error */ + errno = EIO; +out : return ntfs_volume_error(errno); - } static void usage(void) diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index f77a016f..fb1c2fc5 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -198,7 +198,7 @@ static const char *usage_msg = "Copyright (C) 2005-2007 Yura Pakhuchiy\n" "Copyright (C) 2006-2009 Szabolcs Szakacsits\n" "Copyright (C) 2007-2021 Jean-Pierre Andre\n" -"Copyright (C) 2009 Erik Larsson\n" +"Copyright (C) 2009-2020 Erik Larsson\n" "\n" "Usage: %s [-o option[,...]] \n" "\n" @@ -3965,6 +3965,9 @@ static struct fuse_operations ntfs_3g_ops = { .rmdir = ntfs_fuse_rmdir, #ifdef HAVE_UTIMENSAT .utimens = ntfs_fuse_utimens, +#if defined(linux) & !defined(FUSE_INTERNAL) & (FUSE_VERSION < 30) + .flag_utime_omit_ok = 1, +#endif /* defined(linux) & !defined(FUSE_INTERNAL) */ #else .utime = ntfs_fuse_utime, #endif @@ -4053,8 +4056,7 @@ static int ntfs_open(const char *device) !ctx->hide_hid_files, ctx->hide_dot_files)) goto err_out; - ctx->vol->free_clusters = ntfs_attr_get_free_bits(ctx->vol->lcnbmp_na); - if (ctx->vol->free_clusters < 0) { + if (ntfs_volume_get_free_space(ctx->vol)) { ntfs_log_perror("Failed to read NTFS $Bitmap"); goto err_out; } @@ -4073,9 +4075,12 @@ static int ntfs_open(const char *device) } errno = 0; + goto out; err_out: + if (!errno) + errno = EIO; +out : return ntfs_volume_error(errno); - } static void usage(void)