1 /** 2 * efs.c - Limited processing of encrypted files 3 * 4 * This module is part of ntfs-3g library 5 * 6 * Copyright (c) 2009 Martin Bene 7 * Copyright (c) 2009-2010 Jean-Pierre Andre 8 * 9 * This program/include file is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU General Public License as published 11 * by the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program/include file is distributed in the hope that it will be 15 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty 16 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program (in the main directory of the NTFS-3G 21 * distribution in the file COPYING); if not, write to the Free Software 22 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25 #ifdef HAVE_CONFIG_H 26 #include "config.h" 27 #endif 28 29 #ifdef HAVE_STDLIB_H 30 #include <stdlib.h> 31 #endif 32 #ifdef HAVE_ERRNO_H 33 #include <errno.h> 34 #endif 35 #ifdef HAVE_STRING_H 36 #include <string.h> 37 #endif 38 #ifdef HAVE_SYS_STAT_H 39 #include <sys/stat.h> 40 #endif 41 42 #ifdef HAVE_SETXATTR 43 #include <sys/xattr.h> 44 #endif 45 46 #ifdef HAVE_SYS_SYSMACROS_H 47 #include <sys/sysmacros.h> 48 #endif 49 50 #include "types.h" 51 #include "debug.h" 52 #include "attrib.h" 53 #include "inode.h" 54 #include "dir.h" 55 #include "efs.h" 56 #include "index.h" 57 #include "logging.h" 58 #include "misc.h" 59 #include "efs.h" 60 61 #ifdef HAVE_SETXATTR /* extended attributes interface required */ 62 63 static ntfschar logged_utility_stream_name[] = { 64 const_cpu_to_le16('$'), 65 const_cpu_to_le16('E'), 66 const_cpu_to_le16('F'), 67 const_cpu_to_le16('S'), 68 const_cpu_to_le16(0) 69 } ; 70 71 72 /* 73 * Get the ntfs EFS info into an extended attribute 74 */ 75 76 int ntfs_get_efs_info(ntfs_inode *ni, char *value, size_t size) 77 { 78 EFS_ATTR_HEADER *efs_info; 79 s64 attr_size = 0; 80 81 if (ni) { 82 if (ni->flags & FILE_ATTR_ENCRYPTED) { 83 efs_info = (EFS_ATTR_HEADER*)ntfs_attr_readall(ni, 84 AT_LOGGED_UTILITY_STREAM,(ntfschar*)NULL, 0, 85 &attr_size); 86 if (efs_info 87 && (le32_to_cpu(efs_info->length) == attr_size)) { 88 if (attr_size <= (s64)size) { 89 if (value) 90 memcpy(value,efs_info,attr_size); 91 else { 92 errno = EFAULT; 93 attr_size = 0; 94 } 95 } else 96 if (size) { 97 errno = ERANGE; 98 attr_size = 0; 99 } 100 free (efs_info); 101 } else { 102 if (efs_info) { 103 free(efs_info); 104 ntfs_log_error("Bad efs_info for inode %lld\n", 105 (long long)ni->mft_no); 106 } else { 107 ntfs_log_error("Could not get efsinfo" 108 " for inode %lld\n", 109 (long long)ni->mft_no); 110 } 111 errno = EIO; 112 attr_size = 0; 113 } 114 } else { 115 errno = ENODATA; 116 ntfs_log_trace("Inode %lld is not encrypted\n", 117 (long long)ni->mft_no); 118 } 119 } 120 return (attr_size ? (int)attr_size : -errno); 121 } 122 123 /* 124 * Fix all encrypted AT_DATA attributes of an inode 125 * 126 * The fix may require making an attribute non resident, which 127 * requires more space in the MFT record, and may cause some 128 * attribute to be expelled and the full record to be reorganized. 129 * When this happens, the search for data attributes has to be 130 * reinitialized. 131 * 132 * Returns zero if successful. 133 * -1 if there is a problem. 134 */ 135 136 static int fixup_loop(ntfs_inode *ni) 137 { 138 ntfs_attr_search_ctx *ctx; 139 ntfs_attr *na; 140 ATTR_RECORD *a; 141 BOOL restart; 142 BOOL first; 143 int cnt; 144 int maxcnt; 145 int res = 0; 146 147 maxcnt = 0; 148 do { 149 restart = FALSE; 150 ctx = ntfs_attr_get_search_ctx(ni, NULL); 151 if (!ctx) { 152 ntfs_log_error("Failed to get ctx for efs\n"); 153 res = -1; 154 } 155 cnt = 0; 156 while (!restart && !res 157 && !ntfs_attr_lookup(AT_DATA, NULL, 0, 158 CASE_SENSITIVE, 0, NULL, 0, ctx)) { 159 cnt++; 160 a = ctx->attr; 161 na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA, 162 (ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)), 163 a->name_length); 164 if (!na) { 165 ntfs_log_error("can't open DATA Attribute\n"); 166 res = -1; 167 } 168 if (na && !(ctx->attr->flags & ATTR_IS_ENCRYPTED)) { 169 if (!NAttrNonResident(na) 170 && ntfs_attr_make_non_resident(na, ctx)) { 171 /* 172 * ntfs_attr_make_non_resident fails if there 173 * is not enough space in the MFT record. 174 * When this happens, force making non-resident 175 * so that some other attribute is expelled. 176 */ 177 if (ntfs_attr_force_non_resident(na)) { 178 res = -1; 179 } else { 180 /* make sure there is some progress */ 181 if (cnt <= maxcnt) { 182 errno = EIO; 183 ntfs_log_error("Multiple failure" 184 " making non resident\n"); 185 res = -1; 186 } else { 187 ntfs_attr_put_search_ctx(ctx); 188 ctx = (ntfs_attr_search_ctx*)NULL; 189 restart = TRUE; 190 maxcnt = cnt; 191 } 192 } 193 } 194 if (!restart && !res 195 && ntfs_efs_fixup_attribute(ctx, na)) { 196 ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n"); 197 res = -1; 198 } 199 } 200 if (na) 201 ntfs_attr_close(na); 202 } 203 first = FALSE; 204 } while (restart && !res); 205 if (ctx) 206 ntfs_attr_put_search_ctx(ctx); 207 return (res); 208 } 209 210 /* 211 * Set the efs data from an extended attribute 212 * Warning : the new data is not checked 213 * Returns 0, or -1 if there is a problem 214 */ 215 216 int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, 217 int flags) 218 219 { 220 int res; 221 int written; 222 ntfs_attr *na; 223 const EFS_ATTR_HEADER *info_header; 224 225 res = 0; 226 if (ni && value && size) { 227 if (ni->flags & (FILE_ATTR_ENCRYPTED | FILE_ATTR_COMPRESSED)) { 228 if (ni->flags & FILE_ATTR_ENCRYPTED) { 229 ntfs_log_trace("Inode %lld already encrypted\n", 230 (long long)ni->mft_no); 231 errno = EEXIST; 232 } else { 233 /* 234 * Possible problem : if encrypted file was 235 * restored in a compressed directory, it was 236 * restored as compressed. 237 * TODO : decompress first. 238 */ 239 ntfs_log_error("Inode %lld cannot be encrypted and compressed\n", 240 (long long)ni->mft_no); 241 errno = EIO; 242 } 243 return -1; 244 } 245 info_header = (const EFS_ATTR_HEADER*)value; 246 /* make sure we get a likely efsinfo */ 247 if (le32_to_cpu(info_header->length) != size) { 248 errno = EINVAL; 249 return (-1); 250 } 251 if (!ntfs_attr_exist(ni,AT_LOGGED_UTILITY_STREAM, 252 (ntfschar*)NULL,0)) { 253 if (!(flags & XATTR_REPLACE)) { 254 /* 255 * no logged_utility_stream attribute : add one, 256 * apparently, this does not feed the new value in 257 */ 258 res = ntfs_attr_add(ni,AT_LOGGED_UTILITY_STREAM, 259 logged_utility_stream_name,4, 260 (u8*)NULL,(s64)size); 261 } else { 262 errno = ENODATA; 263 res = -1; 264 } 265 } else { 266 errno = EEXIST; 267 res = -1; 268 } 269 if (!res) { 270 /* 271 * open and update the existing efs data 272 */ 273 na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM, 274 logged_utility_stream_name, 4); 275 if (na) { 276 /* resize attribute */ 277 res = ntfs_attr_truncate(na, (s64)size); 278 /* overwrite value if any */ 279 if (!res && value) { 280 written = (int)ntfs_attr_pwrite(na, 281 (s64)0, (s64)size, value); 282 if (written != (s64)size) { 283 ntfs_log_error("Failed to " 284 "update efs data\n"); 285 errno = EIO; 286 res = -1; 287 } 288 } 289 ntfs_attr_close(na); 290 } else 291 res = -1; 292 } 293 if (!res) { 294 /* Don't handle AT_DATA Attribute(s) if inode is a directory */ 295 if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { 296 /* iterate over AT_DATA attributes */ 297 /* set encrypted flag, truncate attribute to match padding bytes */ 298 299 if (fixup_loop(ni)) 300 return -1; 301 } 302 ni->flags |= FILE_ATTR_ENCRYPTED; 303 NInoSetDirty(ni); 304 NInoFileNameSetDirty(ni); 305 } 306 } else { 307 errno = EINVAL; 308 res = -1; 309 } 310 return (res ? -1 : 0); 311 } 312 313 /* 314 * Fixup raw encrypted AT_DATA Attribute 315 * read padding length from last two bytes 316 * truncate attribute, make non-resident, 317 * set data size to match padding length 318 * set ATTR_IS_ENCRYPTED flag on attribute 319 * 320 * Return 0 if successful 321 * -1 if failed (errno tells why) 322 */ 323 324 int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) 325 { 326 u64 newsize; 327 u64 oldsize; 328 le16 appended_bytes; 329 u16 padding_length; 330 ntfs_inode *ni; 331 BOOL close_ctx = FALSE; 332 333 if (!na) { 334 ntfs_log_error("no na specified for efs_fixup_attribute\n"); 335 goto err_out; 336 } 337 if (!ctx) { 338 ctx = ntfs_attr_get_search_ctx(na->ni, NULL); 339 if (!ctx) { 340 ntfs_log_error("Failed to get ctx for efs\n"); 341 goto err_out; 342 } 343 close_ctx = TRUE; 344 if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, 345 CASE_SENSITIVE, 0, NULL, 0, ctx)) { 346 ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); 347 goto err_out; 348 } 349 } else { 350 if (!NAttrNonResident(na)) { 351 ntfs_log_error("Cannot make non resident" 352 " when a context has been allocated\n"); 353 goto err_out; 354 } 355 } 356 357 /* no extra bytes are added to void attributes */ 358 oldsize = na->data_size; 359 if (oldsize) { 360 /* make sure size is valid for a raw encrypted stream */ 361 if ((oldsize & 511) != 2) { 362 ntfs_log_error("Bad raw encrypted stream\n"); 363 goto err_out; 364 } 365 /* read padding length from last two bytes of attribute */ 366 if (ntfs_attr_pread(na, oldsize - 2, 2, &appended_bytes) != 2) { 367 ntfs_log_error("Error reading padding length\n"); 368 goto err_out; 369 } 370 padding_length = le16_to_cpu(appended_bytes); 371 if (padding_length > 511 || padding_length > na->data_size-2) { 372 errno = EINVAL; 373 ntfs_log_error("invalid padding length %d for data_size %lld\n", 374 padding_length, (long long)oldsize); 375 goto err_out; 376 } 377 newsize = oldsize - padding_length - 2; 378 /* 379 * truncate attribute to possibly free clusters allocated 380 * for the last two bytes, but do not truncate to new size 381 * to avoid losing useful data 382 */ 383 if (ntfs_attr_truncate(na, oldsize - 2)) { 384 ntfs_log_error("Error truncating attribute\n"); 385 goto err_out; 386 } 387 } else 388 newsize = 0; 389 390 /* 391 * Encrypted AT_DATA Attributes MUST be non-resident 392 * This has to be done after the attribute is resized, as 393 * resizing down to zero may cause the attribute to be made 394 * resident. 395 */ 396 if (!NAttrNonResident(na) 397 && ntfs_attr_make_non_resident(na, ctx)) { 398 if (!close_ctx 399 || ntfs_attr_force_non_resident(na)) { 400 ntfs_log_error("Error making DATA attribute non-resident\n"); 401 goto err_out; 402 } else { 403 /* 404 * must reinitialize context after forcing 405 * non-resident. We need a context for updating 406 * the state, and at this point, we are sure 407 * the context is not used elsewhere. 408 */ 409 ntfs_attr_reinit_search_ctx(ctx); 410 if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, 411 CASE_SENSITIVE, 0, NULL, 0, ctx)) { 412 ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); 413 goto err_out; 414 } 415 } 416 } 417 ni = na->ni; 418 if (!na->name_len) { 419 ni->data_size = newsize; 420 ni->allocated_size = na->allocated_size; 421 } 422 NInoSetDirty(ni); 423 NInoFileNameSetDirty(ni); 424 425 ctx->attr->data_size = cpu_to_le64(newsize); 426 if (le64_to_cpu(ctx->attr->initialized_size) > newsize) 427 ctx->attr->initialized_size = ctx->attr->data_size; 428 ctx->attr->flags |= ATTR_IS_ENCRYPTED; 429 if (close_ctx) 430 ntfs_attr_put_search_ctx(ctx); 431 432 return (0); 433 err_out: 434 if (close_ctx && ctx) 435 ntfs_attr_put_search_ctx(ctx); 436 return (-1); 437 } 438 439 #endif /* HAVE_SETXATTR */ 440