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