blob: 7957005b6b629d619b3ecdf5ac69cbdb749d710f
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 | { |
324 | u64 newsize; |
325 | u64 oldsize; |
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 | |
423 | ctx->attr->data_size = cpu_to_le64(newsize); |
424 | if (le64_to_cpu(ctx->attr->initialized_size) > newsize) |
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 */ |
438 |