Blame view

fs/kernel_read_file.c 4.36 KB
5287b07f6   Kees Cook   fs/kernel_read_fi...
1
2
3
4
5
6
  // SPDX-License-Identifier: GPL-2.0-only
  #include <linux/fs.h>
  #include <linux/fs_struct.h>
  #include <linux/kernel_read_file.h>
  #include <linux/security.h>
  #include <linux/vmalloc.h>
113eeb517   Kees Cook   fs/kernel_read_fi...
7
8
9
10
  /**
   * kernel_read_file() - read file contents into a kernel buffer
   *
   * @file	file to read from
0fa8e0846   Kees Cook   fs/kernel_file_re...
11
   * @offset	where to start reading from (see below).
113eeb517   Kees Cook   fs/kernel_read_fi...
12
13
14
15
16
   * @buf		pointer to a "void *" buffer for reading into (if
   *		*@buf is NULL, a buffer will be allocated, and
   *		@buf_size will be ignored)
   * @buf_size	size of buf, if already allocated. If @buf not
   *		allocated, this is the largest size to allocate.
885352881   Kees Cook   fs/kernel_read_fi...
17
18
   * @file_size	if non-NULL, the full size of @file will be
   *		written here.
113eeb517   Kees Cook   fs/kernel_read_fi...
19
20
21
   * @id		the kernel_read_file_id identifying the type of
   *		file contents being read (for LSMs to examine)
   *
0fa8e0846   Kees Cook   fs/kernel_file_re...
22
23
24
25
26
27
28
29
   * @offset must be 0 unless both @buf and @file_size are non-NULL
   * (i.e. the caller must be expecting to read partial file contents
   * via an already-allocated @buf, in at most @buf_size chunks, and
   * will be able to determine when the entire file was read by
   * checking @file_size). This isn't a recommended way to read a
   * file, though, since it is possible that the contents might
   * change between calls to kernel_read_file().
   *
113eeb517   Kees Cook   fs/kernel_read_fi...
30
31
32
33
   * Returns number of bytes read (no single read will be bigger
   * than INT_MAX), or negative on error.
   *
   */
0fa8e0846   Kees Cook   fs/kernel_file_re...
34
  int kernel_read_file(struct file *file, loff_t offset, void **buf,
885352881   Kees Cook   fs/kernel_read_fi...
35
36
  		     size_t buf_size, size_t *file_size,
  		     enum kernel_read_file_id id)
5287b07f6   Kees Cook   fs/kernel_read_fi...
37
38
  {
  	loff_t i_size, pos;
0fa8e0846   Kees Cook   fs/kernel_file_re...
39
  	size_t copied;
5287b07f6   Kees Cook   fs/kernel_read_fi...
40
  	void *allocated = NULL;
0fa8e0846   Kees Cook   fs/kernel_file_re...
41
  	bool whole_file;
5287b07f6   Kees Cook   fs/kernel_read_fi...
42
  	int ret;
0fa8e0846   Kees Cook   fs/kernel_file_re...
43
44
  	if (offset != 0 && (!*buf || !file_size))
  		return -EINVAL;
113eeb517   Kees Cook   fs/kernel_read_fi...
45
  	if (!S_ISREG(file_inode(file)->i_mode))
5287b07f6   Kees Cook   fs/kernel_read_fi...
46
47
48
49
50
  		return -EINVAL;
  
  	ret = deny_write_access(file);
  	if (ret)
  		return ret;
5287b07f6   Kees Cook   fs/kernel_read_fi...
51
52
53
54
55
  	i_size = i_size_read(file_inode(file));
  	if (i_size <= 0) {
  		ret = -EINVAL;
  		goto out;
  	}
0fa8e0846   Kees Cook   fs/kernel_file_re...
56
57
58
59
60
61
62
  	/* The file is too big for sane activities. */
  	if (i_size > INT_MAX) {
  		ret = -EFBIG;
  		goto out;
  	}
  	/* The entire file cannot be read in one buffer. */
  	if (!file_size && offset == 0 && i_size > buf_size) {
5287b07f6   Kees Cook   fs/kernel_read_fi...
63
64
65
  		ret = -EFBIG;
  		goto out;
  	}
0fa8e0846   Kees Cook   fs/kernel_file_re...
66
67
68
69
70
  
  	whole_file = (offset == 0 && i_size <= buf_size);
  	ret = security_kernel_read_file(file, id, whole_file);
  	if (ret)
  		goto out;
885352881   Kees Cook   fs/kernel_read_fi...
71
72
  	if (file_size)
  		*file_size = i_size;
5287b07f6   Kees Cook   fs/kernel_read_fi...
73
74
75
76
77
78
79
  
  	if (!*buf)
  		*buf = allocated = vmalloc(i_size);
  	if (!*buf) {
  		ret = -ENOMEM;
  		goto out;
  	}
0fa8e0846   Kees Cook   fs/kernel_file_re...
80
81
82
83
84
85
86
87
  	pos = offset;
  	copied = 0;
  	while (copied < buf_size) {
  		ssize_t bytes;
  		size_t wanted = min_t(size_t, buf_size - copied,
  					      i_size - pos);
  
  		bytes = kernel_read(file, *buf + copied, wanted, &pos);
5287b07f6   Kees Cook   fs/kernel_read_fi...
88
89
90
91
92
93
94
  		if (bytes < 0) {
  			ret = bytes;
  			goto out_free;
  		}
  
  		if (bytes == 0)
  			break;
0fa8e0846   Kees Cook   fs/kernel_file_re...
95
  		copied += bytes;
5287b07f6   Kees Cook   fs/kernel_read_fi...
96
  	}
0fa8e0846   Kees Cook   fs/kernel_file_re...
97
98
99
100
101
  	if (whole_file) {
  		if (pos != i_size) {
  			ret = -EIO;
  			goto out_free;
  		}
5287b07f6   Kees Cook   fs/kernel_read_fi...
102

0fa8e0846   Kees Cook   fs/kernel_file_re...
103
104
  		ret = security_kernel_post_read_file(file, *buf, i_size, id);
  	}
5287b07f6   Kees Cook   fs/kernel_read_fi...
105
106
107
108
109
110
111
112
113
114
115
  
  out_free:
  	if (ret < 0) {
  		if (allocated) {
  			vfree(*buf);
  			*buf = NULL;
  		}
  	}
  
  out:
  	allow_write_access(file);
0fa8e0846   Kees Cook   fs/kernel_file_re...
116
  	return ret == 0 ? copied : ret;
5287b07f6   Kees Cook   fs/kernel_read_fi...
117
118
  }
  EXPORT_SYMBOL_GPL(kernel_read_file);
0fa8e0846   Kees Cook   fs/kernel_file_re...
119
  int kernel_read_file_from_path(const char *path, loff_t offset, void **buf,
885352881   Kees Cook   fs/kernel_read_fi...
120
121
  			       size_t buf_size, size_t *file_size,
  			       enum kernel_read_file_id id)
5287b07f6   Kees Cook   fs/kernel_read_fi...
122
123
124
125
126
127
128
129
130
131
  {
  	struct file *file;
  	int ret;
  
  	if (!path || !*path)
  		return -EINVAL;
  
  	file = filp_open(path, O_RDONLY, 0);
  	if (IS_ERR(file))
  		return PTR_ERR(file);
0fa8e0846   Kees Cook   fs/kernel_file_re...
132
  	ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
5287b07f6   Kees Cook   fs/kernel_read_fi...
133
134
135
136
  	fput(file);
  	return ret;
  }
  EXPORT_SYMBOL_GPL(kernel_read_file_from_path);
0fa8e0846   Kees Cook   fs/kernel_file_re...
137
138
139
  int kernel_read_file_from_path_initns(const char *path, loff_t offset,
  				      void **buf, size_t buf_size,
  				      size_t *file_size,
5287b07f6   Kees Cook   fs/kernel_read_fi...
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
  				      enum kernel_read_file_id id)
  {
  	struct file *file;
  	struct path root;
  	int ret;
  
  	if (!path || !*path)
  		return -EINVAL;
  
  	task_lock(&init_task);
  	get_fs_root(init_task.fs, &root);
  	task_unlock(&init_task);
  
  	file = file_open_root(root.dentry, root.mnt, path, O_RDONLY, 0);
  	path_put(&root);
  	if (IS_ERR(file))
  		return PTR_ERR(file);
0fa8e0846   Kees Cook   fs/kernel_file_re...
157
  	ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
5287b07f6   Kees Cook   fs/kernel_read_fi...
158
159
160
161
  	fput(file);
  	return ret;
  }
  EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns);
0fa8e0846   Kees Cook   fs/kernel_file_re...
162
163
  int kernel_read_file_from_fd(int fd, loff_t offset, void **buf,
  			     size_t buf_size, size_t *file_size,
5287b07f6   Kees Cook   fs/kernel_read_fi...
164
165
166
167
168
169
170
  			     enum kernel_read_file_id id)
  {
  	struct fd f = fdget(fd);
  	int ret = -EBADF;
  
  	if (!f.file)
  		goto out;
0fa8e0846   Kees Cook   fs/kernel_file_re...
171
  	ret = kernel_read_file(f.file, offset, buf, buf_size, file_size, id);
5287b07f6   Kees Cook   fs/kernel_read_fi...
172
173
174
175
176
  out:
  	fdput(f);
  	return ret;
  }
  EXPORT_SYMBOL_GPL(kernel_read_file_from_fd);