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