summaryrefslogtreecommitdiff
path: root/libntfs-3g/efs.c (plain)
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
63static 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
76int 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
136static 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
214int 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
322int 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);
431err_out:
432 if (close_ctx && ctx)
433 ntfs_attr_put_search_ctx(ctx);
434 return (-1);
435}
436
437#endif /* HAVE_SETXATTR */
438