xref: /haiku/src/add-ons/kernel/file_systems/ntfs/libntfs/efs.c (revision f2cc03fed3b4d7e592f550c9b86f72a887897b58)
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