Steve Kondik | 2111ad7 | 2013-07-07 12:07:44 -0700 | [diff] [blame] | 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 | int cnt; |
| 143 | int maxcnt; |
| 144 | int res = 0; |
| 145 | |
| 146 | maxcnt = 0; |
| 147 | do { |
| 148 | restart = FALSE; |
| 149 | ctx = ntfs_attr_get_search_ctx(ni, NULL); |
| 150 | if (!ctx) { |
| 151 | ntfs_log_error("Failed to get ctx for efs\n"); |
| 152 | res = -1; |
| 153 | } |
| 154 | cnt = 0; |
| 155 | while (!restart && !res |
| 156 | && !ntfs_attr_lookup(AT_DATA, NULL, 0, |
| 157 | CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| 158 | cnt++; |
| 159 | a = ctx->attr; |
| 160 | na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA, |
| 161 | (ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)), |
| 162 | a->name_length); |
| 163 | if (!na) { |
| 164 | ntfs_log_error("can't open DATA Attribute\n"); |
| 165 | res = -1; |
| 166 | } |
| 167 | if (na && !(ctx->attr->flags & ATTR_IS_ENCRYPTED)) { |
| 168 | if (!NAttrNonResident(na) |
| 169 | && ntfs_attr_make_non_resident(na, ctx)) { |
| 170 | /* |
| 171 | * ntfs_attr_make_non_resident fails if there |
| 172 | * is not enough space in the MFT record. |
| 173 | * When this happens, force making non-resident |
| 174 | * so that some other attribute is expelled. |
| 175 | */ |
| 176 | if (ntfs_attr_force_non_resident(na)) { |
| 177 | res = -1; |
| 178 | } else { |
| 179 | /* make sure there is some progress */ |
| 180 | if (cnt <= maxcnt) { |
| 181 | errno = EIO; |
| 182 | ntfs_log_error("Multiple failure" |
| 183 | " making non resident\n"); |
| 184 | res = -1; |
| 185 | } else { |
| 186 | ntfs_attr_put_search_ctx(ctx); |
| 187 | ctx = (ntfs_attr_search_ctx*)NULL; |
| 188 | restart = TRUE; |
| 189 | maxcnt = cnt; |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | if (!restart && !res |
| 194 | && ntfs_efs_fixup_attribute(ctx, na)) { |
| 195 | ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n"); |
| 196 | res = -1; |
| 197 | } |
| 198 | } |
| 199 | if (na) |
| 200 | ntfs_attr_close(na); |
| 201 | } |
| 202 | } while (restart && !res); |
| 203 | if (ctx) |
| 204 | ntfs_attr_put_search_ctx(ctx); |
| 205 | return (res); |
| 206 | } |
| 207 | |
| 208 | /* |
| 209 | * Set the efs data from an extended attribute |
| 210 | * Warning : the new data is not checked |
| 211 | * Returns 0, or -1 if there is a problem |
| 212 | */ |
| 213 | |
| 214 | int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size, |
| 215 | int flags) |
| 216 | |
| 217 | { |
| 218 | int res; |
| 219 | int written; |
| 220 | ntfs_attr *na; |
| 221 | const EFS_ATTR_HEADER *info_header; |
| 222 | |
| 223 | res = 0; |
| 224 | if (ni && value && size) { |
| 225 | if (ni->flags & (FILE_ATTR_ENCRYPTED | FILE_ATTR_COMPRESSED)) { |
| 226 | if (ni->flags & FILE_ATTR_ENCRYPTED) { |
| 227 | ntfs_log_trace("Inode %lld already encrypted\n", |
| 228 | (long long)ni->mft_no); |
| 229 | errno = EEXIST; |
| 230 | } else { |
| 231 | /* |
| 232 | * Possible problem : if encrypted file was |
| 233 | * restored in a compressed directory, it was |
| 234 | * restored as compressed. |
| 235 | * TODO : decompress first. |
| 236 | */ |
| 237 | ntfs_log_error("Inode %lld cannot be encrypted and compressed\n", |
| 238 | (long long)ni->mft_no); |
| 239 | errno = EIO; |
| 240 | } |
| 241 | return -1; |
| 242 | } |
| 243 | info_header = (const EFS_ATTR_HEADER*)value; |
| 244 | /* make sure we get a likely efsinfo */ |
| 245 | if (le32_to_cpu(info_header->length) != size) { |
| 246 | errno = EINVAL; |
| 247 | return (-1); |
| 248 | } |
| 249 | if (!ntfs_attr_exist(ni,AT_LOGGED_UTILITY_STREAM, |
| 250 | (ntfschar*)NULL,0)) { |
| 251 | if (!(flags & XATTR_REPLACE)) { |
| 252 | /* |
| 253 | * no logged_utility_stream attribute : add one, |
| 254 | * apparently, this does not feed the new value in |
| 255 | */ |
| 256 | res = ntfs_attr_add(ni,AT_LOGGED_UTILITY_STREAM, |
| 257 | logged_utility_stream_name,4, |
| 258 | (u8*)NULL,(s64)size); |
| 259 | } else { |
| 260 | errno = ENODATA; |
| 261 | res = -1; |
| 262 | } |
| 263 | } else { |
| 264 | errno = EEXIST; |
| 265 | res = -1; |
| 266 | } |
| 267 | if (!res) { |
| 268 | /* |
| 269 | * open and update the existing efs data |
| 270 | */ |
| 271 | na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM, |
| 272 | logged_utility_stream_name, 4); |
| 273 | if (na) { |
| 274 | /* resize attribute */ |
| 275 | res = ntfs_attr_truncate(na, (s64)size); |
| 276 | /* overwrite value if any */ |
| 277 | if (!res && value) { |
| 278 | written = (int)ntfs_attr_pwrite(na, |
| 279 | (s64)0, (s64)size, value); |
| 280 | if (written != (s64)size) { |
| 281 | ntfs_log_error("Failed to " |
| 282 | "update efs data\n"); |
| 283 | errno = EIO; |
| 284 | res = -1; |
| 285 | } |
| 286 | } |
| 287 | ntfs_attr_close(na); |
| 288 | } else |
| 289 | res = -1; |
| 290 | } |
| 291 | if (!res) { |
| 292 | /* Don't handle AT_DATA Attribute(s) if inode is a directory */ |
| 293 | if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { |
| 294 | /* iterate over AT_DATA attributes */ |
| 295 | /* set encrypted flag, truncate attribute to match padding bytes */ |
| 296 | |
| 297 | if (fixup_loop(ni)) |
| 298 | return -1; |
| 299 | } |
| 300 | ni->flags |= FILE_ATTR_ENCRYPTED; |
| 301 | NInoSetDirty(ni); |
| 302 | NInoFileNameSetDirty(ni); |
| 303 | } |
| 304 | } else { |
| 305 | errno = EINVAL; |
| 306 | res = -1; |
| 307 | } |
| 308 | return (res ? -1 : 0); |
| 309 | } |
| 310 | |
| 311 | /* |
| 312 | * Fixup raw encrypted AT_DATA Attribute |
| 313 | * read padding length from last two bytes |
| 314 | * truncate attribute, make non-resident, |
| 315 | * set data size to match padding length |
| 316 | * set ATTR_IS_ENCRYPTED flag on attribute |
| 317 | * |
| 318 | * Return 0 if successful |
| 319 | * -1 if failed (errno tells why) |
| 320 | */ |
| 321 | |
| 322 | int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) |
| 323 | { |
Steve Kondik | e68cb60 | 2016-08-28 00:45:36 -0700 | [diff] [blame] | 324 | s64 newsize; |
| 325 | s64 oldsize; |
Steve Kondik | 2111ad7 | 2013-07-07 12:07:44 -0700 | [diff] [blame] | 326 | le16 appended_bytes; |
| 327 | u16 padding_length; |
| 328 | ntfs_inode *ni; |
| 329 | BOOL close_ctx = FALSE; |
| 330 | |
| 331 | if (!na) { |
| 332 | ntfs_log_error("no na specified for efs_fixup_attribute\n"); |
| 333 | goto err_out; |
| 334 | } |
| 335 | if (!ctx) { |
| 336 | ctx = ntfs_attr_get_search_ctx(na->ni, NULL); |
| 337 | if (!ctx) { |
| 338 | ntfs_log_error("Failed to get ctx for efs\n"); |
| 339 | goto err_out; |
| 340 | } |
| 341 | close_ctx = TRUE; |
| 342 | if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, |
| 343 | CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| 344 | ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); |
| 345 | goto err_out; |
| 346 | } |
| 347 | } else { |
| 348 | if (!NAttrNonResident(na)) { |
| 349 | ntfs_log_error("Cannot make non resident" |
| 350 | " when a context has been allocated\n"); |
| 351 | goto err_out; |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | /* no extra bytes are added to void attributes */ |
| 356 | oldsize = na->data_size; |
| 357 | if (oldsize) { |
| 358 | /* make sure size is valid for a raw encrypted stream */ |
| 359 | if ((oldsize & 511) != 2) { |
| 360 | ntfs_log_error("Bad raw encrypted stream\n"); |
| 361 | goto err_out; |
| 362 | } |
| 363 | /* read padding length from last two bytes of attribute */ |
| 364 | if (ntfs_attr_pread(na, oldsize - 2, 2, &appended_bytes) != 2) { |
| 365 | ntfs_log_error("Error reading padding length\n"); |
| 366 | goto err_out; |
| 367 | } |
| 368 | padding_length = le16_to_cpu(appended_bytes); |
| 369 | if (padding_length > 511 || padding_length > na->data_size-2) { |
| 370 | errno = EINVAL; |
| 371 | ntfs_log_error("invalid padding length %d for data_size %lld\n", |
| 372 | padding_length, (long long)oldsize); |
| 373 | goto err_out; |
| 374 | } |
| 375 | newsize = oldsize - padding_length - 2; |
| 376 | /* |
| 377 | * truncate attribute to possibly free clusters allocated |
| 378 | * for the last two bytes, but do not truncate to new size |
| 379 | * to avoid losing useful data |
| 380 | */ |
| 381 | if (ntfs_attr_truncate(na, oldsize - 2)) { |
| 382 | ntfs_log_error("Error truncating attribute\n"); |
| 383 | goto err_out; |
| 384 | } |
| 385 | } else |
| 386 | newsize = 0; |
| 387 | |
| 388 | /* |
| 389 | * Encrypted AT_DATA Attributes MUST be non-resident |
| 390 | * This has to be done after the attribute is resized, as |
| 391 | * resizing down to zero may cause the attribute to be made |
| 392 | * resident. |
| 393 | */ |
| 394 | if (!NAttrNonResident(na) |
| 395 | && ntfs_attr_make_non_resident(na, ctx)) { |
| 396 | if (!close_ctx |
| 397 | || ntfs_attr_force_non_resident(na)) { |
| 398 | ntfs_log_error("Error making DATA attribute non-resident\n"); |
| 399 | goto err_out; |
| 400 | } else { |
| 401 | /* |
| 402 | * must reinitialize context after forcing |
| 403 | * non-resident. We need a context for updating |
| 404 | * the state, and at this point, we are sure |
| 405 | * the context is not used elsewhere. |
| 406 | */ |
| 407 | ntfs_attr_reinit_search_ctx(ctx); |
| 408 | if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, |
| 409 | CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| 410 | ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n"); |
| 411 | goto err_out; |
| 412 | } |
| 413 | } |
| 414 | } |
| 415 | ni = na->ni; |
| 416 | if (!na->name_len) { |
| 417 | ni->data_size = newsize; |
| 418 | ni->allocated_size = na->allocated_size; |
| 419 | } |
| 420 | NInoSetDirty(ni); |
| 421 | NInoFileNameSetDirty(ni); |
| 422 | |
Steve Kondik | e68cb60 | 2016-08-28 00:45:36 -0700 | [diff] [blame] | 423 | ctx->attr->data_size = cpu_to_sle64(newsize); |
| 424 | if (sle64_to_cpu(ctx->attr->initialized_size) > newsize) |
Steve Kondik | 2111ad7 | 2013-07-07 12:07:44 -0700 | [diff] [blame] | 425 | ctx->attr->initialized_size = ctx->attr->data_size; |
| 426 | ctx->attr->flags |= ATTR_IS_ENCRYPTED; |
| 427 | if (close_ctx) |
| 428 | ntfs_attr_put_search_ctx(ctx); |
| 429 | |
| 430 | return (0); |
| 431 | err_out: |
| 432 | if (close_ctx && ctx) |
| 433 | ntfs_attr_put_search_ctx(ctx); |
| 434 | return (-1); |
| 435 | } |
| 436 | |
| 437 | #endif /* HAVE_SETXATTR */ |