]>
Commit | Line | Data |
---|---|---|
a0402406 GKH |
1 | From 4c0c03ca54f72fdd5912516ad0a23ec5cf01bda7 Mon Sep 17 00:00:00 2001 |
2 | From: David Howells <dhowells@redhat.com> | |
3 | Date: Thu, 22 Jul 2010 12:53:18 +0100 | |
4 | Subject: CIFS: Fix a malicious redirect problem in the DNS lookup code | |
5 | ||
6 | From: David Howells <dhowells@redhat.com> | |
7 | ||
8 | commit 4c0c03ca54f72fdd5912516ad0a23ec5cf01bda7 upstream. | |
9 | ||
10 | Fix the security problem in the CIFS filesystem DNS lookup code in which a | |
11 | malicious redirect could be installed by a random user by simply adding a | |
12 | result record into one of their keyrings with add_key() and then invoking a | |
13 | CIFS CFS lookup [CVE-2010-2524]. | |
14 | ||
15 | This is done by creating an internal keyring specifically for the caching of | |
16 | DNS lookups. To enforce the use of this keyring, the module init routine | |
17 | creates a set of override credentials with the keyring installed as the thread | |
18 | keyring and instructs request_key() to only install lookup result keys in that | |
19 | keyring. | |
20 | ||
21 | The override is then applied around the call to request_key(). | |
22 | ||
23 | This has some additional benefits when a kernel service uses this module to | |
24 | request a key: | |
25 | ||
26 | (1) The result keys are owned by root, not the user that caused the lookup. | |
27 | ||
28 | (2) The result keys don't pop up in the user's keyrings. | |
29 | ||
30 | (3) The result keys don't come out of the quota of the user that caused the | |
31 | lookup. | |
32 | ||
33 | The keyring can be viewed as root by doing cat /proc/keys: | |
34 | ||
35 | 2a0ca6c3 I----- 1 perm 1f030000 0 0 keyring .dns_resolver: 1/4 | |
36 | ||
37 | It can then be listed with 'keyctl list' by root. | |
38 | ||
39 | # keyctl list 0x2a0ca6c3 | |
40 | 1 key in keyring: | |
41 | 726766307: --alswrv 0 0 dns_resolver: foo.bar.com | |
42 | ||
43 | Signed-off-by: David Howells <dhowells@redhat.com> | |
44 | Reviewed-and-Tested-by: Jeff Layton <jlayton@redhat.com> | |
45 | Acked-by: Steve French <smfrench@gmail.com> | |
46 | Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> | |
47 | Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> | |
48 | ||
49 | --- | |
50 | fs/cifs/cifsfs.c | 6 ++-- | |
51 | fs/cifs/dns_resolve.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ | |
52 | fs/cifs/dns_resolve.h | 4 +- | |
53 | 3 files changed, 74 insertions(+), 5 deletions(-) | |
54 | ||
55 | --- a/fs/cifs/cifsfs.c | |
56 | +++ b/fs/cifs/cifsfs.c | |
57 | @@ -1033,7 +1033,7 @@ init_cifs(void) | |
58 | goto out_unregister_filesystem; | |
59 | #endif | |
60 | #ifdef CONFIG_CIFS_DFS_UPCALL | |
61 | - rc = register_key_type(&key_type_dns_resolver); | |
62 | + rc = cifs_init_dns_resolver(); | |
63 | if (rc) | |
64 | goto out_unregister_key_type; | |
65 | #endif | |
66 | @@ -1045,7 +1045,7 @@ init_cifs(void) | |
67 | ||
68 | out_unregister_resolver_key: | |
69 | #ifdef CONFIG_CIFS_DFS_UPCALL | |
70 | - unregister_key_type(&key_type_dns_resolver); | |
71 | + cifs_exit_dns_resolver(); | |
72 | out_unregister_key_type: | |
73 | #endif | |
74 | #ifdef CONFIG_CIFS_UPCALL | |
75 | @@ -1071,7 +1071,7 @@ exit_cifs(void) | |
76 | cifs_proc_clean(); | |
77 | #ifdef CONFIG_CIFS_DFS_UPCALL | |
78 | cifs_dfs_release_automount_timer(); | |
79 | - unregister_key_type(&key_type_dns_resolver); | |
80 | + cifs_exit_dns_resolver(); | |
81 | #endif | |
82 | #ifdef CONFIG_CIFS_UPCALL | |
83 | unregister_key_type(&cifs_spnego_key_type); | |
84 | --- a/fs/cifs/dns_resolve.c | |
85 | +++ b/fs/cifs/dns_resolve.c | |
86 | @@ -23,12 +23,16 @@ | |
87 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
88 | */ | |
89 | ||
90 | +#include <linux/keyctl.h> | |
91 | +#include <linux/key-type.h> | |
92 | #include <keys/user-type.h> | |
93 | #include "dns_resolve.h" | |
94 | #include "cifsglob.h" | |
95 | #include "cifsproto.h" | |
96 | #include "cifs_debug.h" | |
97 | ||
98 | +static const struct cred *dns_resolver_cache; | |
99 | + | |
100 | /* Checks if supplied name is IP address | |
101 | * returns: | |
102 | * 1 - name is IP | |
103 | @@ -93,6 +97,7 @@ struct key_type key_type_dns_resolver = | |
104 | int | |
105 | dns_resolve_server_name_to_ip(const char *unc, char **ip_addr) | |
106 | { | |
107 | + const struct cred *saved_cred; | |
108 | int rc = -EAGAIN; | |
109 | struct key *rkey = ERR_PTR(-EAGAIN); | |
110 | char *name; | |
111 | @@ -132,8 +137,15 @@ dns_resolve_server_name_to_ip(const char | |
112 | goto skip_upcall; | |
113 | } | |
114 | ||
115 | + saved_cred = override_creds(dns_resolver_cache); | |
116 | rkey = request_key(&key_type_dns_resolver, name, ""); | |
117 | + revert_creds(saved_cred); | |
118 | if (!IS_ERR(rkey)) { | |
119 | + if (!(rkey->perm & KEY_USR_VIEW)) { | |
120 | + down_read(&rkey->sem); | |
121 | + rkey->perm |= KEY_USR_VIEW; | |
122 | + up_read(&rkey->sem); | |
123 | + } | |
124 | len = rkey->type_data.x[0]; | |
125 | data = rkey->payload.data; | |
126 | } else { | |
127 | @@ -164,4 +176,61 @@ out: | |
128 | return rc; | |
129 | } | |
130 | ||
131 | +int __init cifs_init_dns_resolver(void) | |
132 | +{ | |
133 | + struct cred *cred; | |
134 | + struct key *keyring; | |
135 | + int ret; | |
136 | + | |
137 | + printk(KERN_NOTICE "Registering the %s key type\n", | |
138 | + key_type_dns_resolver.name); | |
139 | + | |
140 | + /* create an override credential set with a special thread keyring in | |
141 | + * which DNS requests are cached | |
142 | + * | |
143 | + * this is used to prevent malicious redirections from being installed | |
144 | + * with add_key(). | |
145 | + */ | |
146 | + cred = prepare_kernel_cred(NULL); | |
147 | + if (!cred) | |
148 | + return -ENOMEM; | |
149 | + | |
150 | + keyring = key_alloc(&key_type_keyring, ".dns_resolver", 0, 0, cred, | |
151 | + (KEY_POS_ALL & ~KEY_POS_SETATTR) | | |
152 | + KEY_USR_VIEW | KEY_USR_READ, | |
153 | + KEY_ALLOC_NOT_IN_QUOTA); | |
154 | + if (IS_ERR(keyring)) { | |
155 | + ret = PTR_ERR(keyring); | |
156 | + goto failed_put_cred; | |
157 | + } | |
158 | + | |
159 | + ret = key_instantiate_and_link(keyring, NULL, 0, NULL, NULL); | |
160 | + if (ret < 0) | |
161 | + goto failed_put_key; | |
162 | + | |
163 | + ret = register_key_type(&key_type_dns_resolver); | |
164 | + if (ret < 0) | |
165 | + goto failed_put_key; | |
166 | + | |
167 | + /* instruct request_key() to use this special keyring as a cache for | |
168 | + * the results it looks up */ | |
169 | + cred->thread_keyring = keyring; | |
170 | + cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING; | |
171 | + dns_resolver_cache = cred; | |
172 | + return 0; | |
173 | + | |
174 | +failed_put_key: | |
175 | + key_put(keyring); | |
176 | +failed_put_cred: | |
177 | + put_cred(cred); | |
178 | + return ret; | |
179 | +} | |
180 | ||
181 | +void __exit cifs_exit_dns_resolver(void) | |
182 | +{ | |
183 | + key_revoke(dns_resolver_cache->thread_keyring); | |
184 | + unregister_key_type(&key_type_dns_resolver); | |
185 | + put_cred(dns_resolver_cache); | |
186 | + printk(KERN_NOTICE "Unregistered %s key type\n", | |
187 | + key_type_dns_resolver.name); | |
188 | +} | |
189 | --- a/fs/cifs/dns_resolve.h | |
190 | +++ b/fs/cifs/dns_resolve.h | |
191 | @@ -24,8 +24,8 @@ | |
192 | #define _DNS_RESOLVE_H | |
193 | ||
194 | #ifdef __KERNEL__ | |
195 | -#include <linux/key-type.h> | |
196 | -extern struct key_type key_type_dns_resolver; | |
197 | +extern int __init cifs_init_dns_resolver(void); | |
198 | +extern void __exit cifs_exit_dns_resolver(void); | |
199 | extern int dns_resolve_server_name_to_ip(const char *unc, char **ip_addr); | |
200 | #endif /* KERNEL */ | |
201 |