readdir.c 13.6 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6 7
/*
 *  linux/fs/readdir.c
 *
 *  Copyright (C) 1995  Linus Torvalds
 */

8
#include <linux/stddef.h>
9
#include <linux/kernel.h>
10
#include <linux/export.h>
Linus Torvalds's avatar
Linus Torvalds committed
11 12 13 14 15 16
#include <linux/time.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/file.h>
#include <linux/fs.h>
17
#include <linux/fsnotify.h>
Linus Torvalds's avatar
Linus Torvalds committed
18 19 20 21
#include <linux/dirent.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
22
#include <linux/compat.h>
23
#include <linux/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
24

25 26 27 28 29 30 31 32 33 34
#include <asm/unaligned.h>

/*
 * Note the "unsafe_put_user() semantics: we goto a
 * label for errors.
 */
#define unsafe_copy_dirent_name(_dst, _src, _len, label) do {	\
	char __user *dst = (_dst);				\
	const char *src = (_src);				\
	size_t len = (_len);					\
35 36
	unsafe_put_user(0, dst+len, label);			\
	unsafe_copy_to_user(dst, src, len, label);		\
37 38 39
} while (0)


40
int iterate_dir(struct file *file, struct dir_context *ctx)
Linus Torvalds's avatar
Linus Torvalds committed
41
{
Al Viro's avatar
Al Viro committed
42
	struct inode *inode = file_inode(file);
43
	bool shared = false;
Linus Torvalds's avatar
Linus Torvalds committed
44
	int res = -ENOTDIR;
45 46 47
	if (file->f_op->iterate_shared)
		shared = true;
	else if (!file->f_op->iterate)
Linus Torvalds's avatar
Linus Torvalds committed
48 49 50 51 52 53
		goto out;

	res = security_file_permission(file, MAY_READ);
	if (res)
		goto out;

54 55 56
	if (shared)
		res = down_read_killable(&inode->i_rwsem);
	else
57
		res = down_write_killable(&inode->i_rwsem);
58 59
	if (res)
		goto out;
60

Linus Torvalds's avatar
Linus Torvalds committed
61 62
	res = -ENOENT;
	if (!IS_DEADDIR(inode)) {
Al Viro's avatar
Al Viro committed
63
		ctx->pos = file->f_pos;
64 65 66 67
		if (shared)
			res = file->f_op->iterate_shared(file, ctx);
		else
			res = file->f_op->iterate(file, ctx);
Al Viro's avatar
Al Viro committed
68
		file->f_pos = ctx->pos;
69
		fsnotify_access(file);
Linus Torvalds's avatar
Linus Torvalds committed
70 71
		file_accessed(file);
	}
72 73 74 75
	if (shared)
		inode_unlock_shared(inode);
	else
		inode_unlock(inode);
Linus Torvalds's avatar
Linus Torvalds committed
76 77 78
out:
	return res;
}
79
EXPORT_SYMBOL(iterate_dir);
Linus Torvalds's avatar
Linus Torvalds committed
80

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
/*
 * POSIX says that a dirent name cannot contain NULL or a '/'.
 *
 * It's not 100% clear what we should really do in this case.
 * The filesystem is clearly corrupted, but returning a hard
 * error means that you now don't see any of the other names
 * either, so that isn't a perfect alternative.
 *
 * And if you return an error, what error do you use? Several
 * filesystems seem to have decided on EUCLEAN being the error
 * code for EFSCORRUPTED, and that may be the error to use. Or
 * just EIO, which is perhaps more obvious to users.
 *
 * In order to see the other file names in the directory, the
 * caller might want to make this a "soft" error: skip the
 * entry, and return the error at the end instead.
 *
 * Note that this should likely do a "memchr(name, 0, len)"
 * check too, since that would be filesystem corruption as
 * well. However, that case can't actually confuse user space,
 * which has to do a strlen() on the name anyway to find the
 * filename length, and the above "soft error" worry means
 * that it's probably better left alone until we have that
 * issue clarified.
105 106 107 108
 *
 * Note the PATH_MAX check - it's arbitrary but the real
 * kernel limit on a possible path component, not NAME_MAX,
 * which is the technical standard limit.
109 110 111
 */
static int verify_dirent_name(const char *name, int len)
{
112
	if (len <= 0 || len >= PATH_MAX)
113
		return -EIO;
114
	if (memchr(name, '/', len))
115 116 117 118
		return -EIO;
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
/*
 * Traditional linux readdir() handling..
 *
 * "count=1" is a special case, meaning that the buffer is one
 * dirent-structure in size and that the code can't handle more
 * anyway. Thus the special "fillonedir()" function for that
 * case (the low-level handlers don't need to care about this).
 */

#ifdef __ARCH_WANT_OLD_READDIR

struct old_linux_dirent {
	unsigned long	d_ino;
	unsigned long	d_offset;
	unsigned short	d_namlen;
	char		d_name[1];
};

struct readdir_callback {
138
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
139 140 141 142
	struct old_linux_dirent __user * dirent;
	int result;
};

143 144
static int fillonedir(struct dir_context *ctx, const char *name, int namlen,
		      loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
145
{
146 147
	struct readdir_callback *buf =
		container_of(ctx, struct readdir_callback, ctx);
Linus Torvalds's avatar
Linus Torvalds committed
148
	struct old_linux_dirent __user * dirent;
149
	unsigned long d_ino;
Linus Torvalds's avatar
Linus Torvalds committed
150 151 152

	if (buf->result)
		return -EINVAL;
153
	d_ino = ino;
154 155
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->result = -EOVERFLOW;
156
		return -EOVERFLOW;
157
	}
Linus Torvalds's avatar
Linus Torvalds committed
158 159
	buf->result++;
	dirent = buf->dirent;
160
	if (!access_ok(dirent,
Linus Torvalds's avatar
Linus Torvalds committed
161 162 163
			(unsigned long)(dirent->d_name + namlen + 1) -
				(unsigned long)dirent))
		goto efault;
164
	if (	__put_user(d_ino, &dirent->d_ino) ||
Linus Torvalds's avatar
Linus Torvalds committed
165 166 167 168 169 170 171 172 173 174 175
		__put_user(offset, &dirent->d_offset) ||
		__put_user(namlen, &dirent->d_namlen) ||
		__copy_to_user(dirent->d_name, name, namlen) ||
		__put_user(0, dirent->d_name + namlen))
		goto efault;
	return 0;
efault:
	buf->result = -EFAULT;
	return -EFAULT;
}

176 177
SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
		struct old_linux_dirent __user *, dirent, unsigned int, count)
Linus Torvalds's avatar
Linus Torvalds committed
178 179
{
	int error;
180
	struct fd f = fdget_pos(fd);
Al Viro's avatar
Al Viro committed
181 182 183 184
	struct readdir_callback buf = {
		.ctx.actor = fillonedir,
		.dirent = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
185

186
	if (!f.file)
187
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
188

189
	error = iterate_dir(f.file, &buf.ctx);
190
	if (buf.result)
Linus Torvalds's avatar
Linus Torvalds committed
191 192
		error = buf.result;

193
	fdput_pos(f);
Linus Torvalds's avatar
Linus Torvalds committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
	return error;
}

#endif /* __ARCH_WANT_OLD_READDIR */

/*
 * New, all-improved, singing, dancing, iBCS2-compliant getdents()
 * interface. 
 */
struct linux_dirent {
	unsigned long	d_ino;
	unsigned long	d_off;
	unsigned short	d_reclen;
	char		d_name[1];
};

struct getdents_callback {
211
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
212
	struct linux_dirent __user * current_dir;
213
	int prev_reclen;
Linus Torvalds's avatar
Linus Torvalds committed
214 215 216 217
	int count;
	int error;
};

218 219
static int filldir(struct dir_context *ctx, const char *name, int namlen,
		   loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
220
{
221
	struct linux_dirent __user *dirent, *prev;
222 223
	struct getdents_callback *buf =
		container_of(ctx, struct getdents_callback, ctx);
224
	unsigned long d_ino;
225 226
	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
		sizeof(long));
227
	int prev_reclen;
Linus Torvalds's avatar
Linus Torvalds committed
228

229 230 231
	buf->error = verify_dirent_name(name, namlen);
	if (unlikely(buf->error))
		return buf->error;
Linus Torvalds's avatar
Linus Torvalds committed
232 233 234
	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
235
	d_ino = ino;
236 237
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->error = -EOVERFLOW;
238
		return -EOVERFLOW;
239
	}
240 241
	prev_reclen = buf->prev_reclen;
	if (prev_reclen && signal_pending(current))
242 243
		return -EINTR;
	dirent = buf->current_dir;
244 245 246 247 248 249
	prev = (void __user *) dirent - prev_reclen;
	if (!user_access_begin(prev, reclen + prev_reclen))
		goto efault;

	/* This might be 'dirent->d_off', but if so it will get overwritten */
	unsafe_put_user(offset, &prev->d_off, efault_end);
250 251 252 253 254 255
	unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
	unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
	unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end);
	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
	user_access_end();

256 257
	buf->current_dir = (void __user *)dirent + reclen;
	buf->prev_reclen = reclen;
Linus Torvalds's avatar
Linus Torvalds committed
258 259
	buf->count -= reclen;
	return 0;
260 261
efault_end:
	user_access_end();
Linus Torvalds's avatar
Linus Torvalds committed
262 263 264 265 266
efault:
	buf->error = -EFAULT;
	return -EFAULT;
}

267 268
SYSCALL_DEFINE3(getdents, unsigned int, fd,
		struct linux_dirent __user *, dirent, unsigned int, count)
Linus Torvalds's avatar
Linus Torvalds committed
269
{
270
	struct fd f;
Al Viro's avatar
Al Viro committed
271 272 273 274 275
	struct getdents_callback buf = {
		.ctx.actor = filldir,
		.count = count,
		.current_dir = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
276 277
	int error;

278
	if (!access_ok(dirent, count))
279
		return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
280

281
	f = fdget_pos(fd);
282
	if (!f.file)
283
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
284

285
	error = iterate_dir(f.file, &buf.ctx);
286 287
	if (error >= 0)
		error = buf.error;
288 289 290 291
	if (buf.prev_reclen) {
		struct linux_dirent __user * lastdirent;
		lastdirent = (void __user *)buf.current_dir - buf.prev_reclen;

292
		if (put_user(buf.ctx.pos, &lastdirent->d_off))
Linus Torvalds's avatar
Linus Torvalds committed
293 294 295 296
			error = -EFAULT;
		else
			error = count - buf.count;
	}
297
	fdput_pos(f);
Linus Torvalds's avatar
Linus Torvalds committed
298 299 300 301
	return error;
}

struct getdents_callback64 {
302
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
303
	struct linux_dirent64 __user * current_dir;
304
	int prev_reclen;
Linus Torvalds's avatar
Linus Torvalds committed
305 306 307 308
	int count;
	int error;
};

309 310
static int filldir64(struct dir_context *ctx, const char *name, int namlen,
		     loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
311
{
312
	struct linux_dirent64 __user *dirent, *prev;
313 314
	struct getdents_callback64 *buf =
		container_of(ctx, struct getdents_callback64, ctx);
315 316
	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
		sizeof(u64));
317
	int prev_reclen;
Linus Torvalds's avatar
Linus Torvalds committed
318

319 320 321
	buf->error = verify_dirent_name(name, namlen);
	if (unlikely(buf->error))
		return buf->error;
Linus Torvalds's avatar
Linus Torvalds committed
322 323 324
	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
325 326
	prev_reclen = buf->prev_reclen;
	if (prev_reclen && signal_pending(current))
327 328
		return -EINTR;
	dirent = buf->current_dir;
329 330 331 332 333 334
	prev = (void __user *)dirent - prev_reclen;
	if (!user_access_begin(prev, reclen + prev_reclen))
		goto efault;

	/* This might be 'dirent->d_off', but if so it will get overwritten */
	unsafe_put_user(offset, &prev->d_off, efault_end);
335 336 337 338 339 340
	unsafe_put_user(ino, &dirent->d_ino, efault_end);
	unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
	unsafe_put_user(d_type, &dirent->d_type, efault_end);
	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
	user_access_end();

341 342
	buf->prev_reclen = reclen;
	buf->current_dir = (void __user *)dirent + reclen;
Linus Torvalds's avatar
Linus Torvalds committed
343 344
	buf->count -= reclen;
	return 0;
345

346 347
efault_end:
	user_access_end();
Linus Torvalds's avatar
Linus Torvalds committed
348 349 350 351 352
efault:
	buf->error = -EFAULT;
	return -EFAULT;
}

353 354
int ksys_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent,
		    unsigned int count)
Linus Torvalds's avatar
Linus Torvalds committed
355
{
356
	struct fd f;
Al Viro's avatar
Al Viro committed
357 358 359 360 361
	struct getdents_callback64 buf = {
		.ctx.actor = filldir64,
		.count = count,
		.current_dir = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
362 363
	int error;

364
	if (!access_ok(dirent, count))
365
		return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
366

367
	f = fdget_pos(fd);
368
	if (!f.file)
369
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
370

371
	error = iterate_dir(f.file, &buf.ctx);
372 373
	if (error >= 0)
		error = buf.error;
374 375
	if (buf.prev_reclen) {
		struct linux_dirent64 __user * lastdirent;
376
		typeof(lastdirent->d_off) d_off = buf.ctx.pos;
377 378

		lastdirent = (void __user *) buf.current_dir - buf.prev_reclen;
Linus Torvalds's avatar
Linus Torvalds committed
379
		if (__put_user(d_off, &lastdirent->d_off))
380 381 382
			error = -EFAULT;
		else
			error = count - buf.count;
Linus Torvalds's avatar
Linus Torvalds committed
383
	}
384
	fdput_pos(f);
Linus Torvalds's avatar
Linus Torvalds committed
385 386
	return error;
}
387

388 389 390 391 392 393 394

SYSCALL_DEFINE3(getdents64, unsigned int, fd,
		struct linux_dirent64 __user *, dirent, unsigned int, count)
{
	return ksys_getdents64(fd, dirent, count);
}

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
#ifdef CONFIG_COMPAT
struct compat_old_linux_dirent {
	compat_ulong_t	d_ino;
	compat_ulong_t	d_offset;
	unsigned short	d_namlen;
	char		d_name[1];
};

struct compat_readdir_callback {
	struct dir_context ctx;
	struct compat_old_linux_dirent __user *dirent;
	int result;
};

static int compat_fillonedir(struct dir_context *ctx, const char *name,
			     int namlen, loff_t offset, u64 ino,
			     unsigned int d_type)
{
	struct compat_readdir_callback *buf =
		container_of(ctx, struct compat_readdir_callback, ctx);
	struct compat_old_linux_dirent __user *dirent;
	compat_ulong_t d_ino;

	if (buf->result)
		return -EINVAL;
	d_ino = ino;
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->result = -EOVERFLOW;
		return -EOVERFLOW;
	}
	buf->result++;
	dirent = buf->dirent;
427
	if (!access_ok(dirent,
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
			(unsigned long)(dirent->d_name + namlen + 1) -
				(unsigned long)dirent))
		goto efault;
	if (	__put_user(d_ino, &dirent->d_ino) ||
		__put_user(offset, &dirent->d_offset) ||
		__put_user(namlen, &dirent->d_namlen) ||
		__copy_to_user(dirent->d_name, name, namlen) ||
		__put_user(0, dirent->d_name + namlen))
		goto efault;
	return 0;
efault:
	buf->result = -EFAULT;
	return -EFAULT;
}

COMPAT_SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
		struct compat_old_linux_dirent __user *, dirent, unsigned int, count)
{
	int error;
	struct fd f = fdget_pos(fd);
	struct compat_readdir_callback buf = {
		.ctx.actor = compat_fillonedir,
		.dirent = dirent
	};

	if (!f.file)
		return -EBADF;

	error = iterate_dir(f.file, &buf.ctx);
	if (buf.result)
		error = buf.result;

	fdput_pos(f);
	return error;
}

struct compat_linux_dirent {
	compat_ulong_t	d_ino;
	compat_ulong_t	d_off;
	unsigned short	d_reclen;
	char		d_name[1];
};

struct compat_getdents_callback {
	struct dir_context ctx;
	struct compat_linux_dirent __user *current_dir;
	struct compat_linux_dirent __user *previous;
	int count;
	int error;
};

static int compat_filldir(struct dir_context *ctx, const char *name, int namlen,
		loff_t offset, u64 ino, unsigned int d_type)
{
	struct compat_linux_dirent __user * dirent;
	struct compat_getdents_callback *buf =
		container_of(ctx, struct compat_getdents_callback, ctx);
	compat_ulong_t d_ino;
	int reclen = ALIGN(offsetof(struct compat_linux_dirent, d_name) +
		namlen + 2, sizeof(compat_long_t));

	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
	d_ino = ino;
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->error = -EOVERFLOW;
		return -EOVERFLOW;
	}
	dirent = buf->previous;
	if (dirent) {
		if (signal_pending(current))
			return -EINTR;
		if (__put_user(offset, &dirent->d_off))
			goto efault;
	}
	dirent = buf->current_dir;
	if (__put_user(d_ino, &dirent->d_ino))
		goto efault;
	if (__put_user(reclen, &dirent->d_reclen))
		goto efault;
	if (copy_to_user(dirent->d_name, name, namlen))
		goto efault;
	if (__put_user(0, dirent->d_name + namlen))
		goto efault;
	if (__put_user(d_type, (char  __user *) dirent + reclen - 1))
		goto efault;
	buf->previous = dirent;
	dirent = (void __user *)dirent + reclen;
	buf->current_dir = dirent;
	buf->count -= reclen;
	return 0;
efault:
	buf->error = -EFAULT;
	return -EFAULT;
}

COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
		struct compat_linux_dirent __user *, dirent, unsigned int, count)
{
	struct fd f;
	struct compat_linux_dirent __user * lastdirent;
	struct compat_getdents_callback buf = {
		.ctx.actor = compat_filldir,
		.current_dir = dirent,
		.count = count
	};
	int error;

537
	if (!access_ok(dirent, count))
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
		return -EFAULT;

	f = fdget_pos(fd);
	if (!f.file)
		return -EBADF;

	error = iterate_dir(f.file, &buf.ctx);
	if (error >= 0)
		error = buf.error;
	lastdirent = buf.previous;
	if (lastdirent) {
		if (put_user(buf.ctx.pos, &lastdirent->d_off))
			error = -EFAULT;
		else
			error = count - buf.count;
	}
	fdput_pos(f);
	return error;
}
#endif