diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2005-09-07 21:28:51 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-09-08 11:14:11 -0400 |
commit | 8920e8f94c44e31a73bdf923b04721e26e88cadd (patch) | |
tree | 7a0195643c37c63335224358256fab8cd445a671 /net/compat.c | |
parent | 5aa3b610a7330c3cd6f0cb264d2189a3a1dcf534 (diff) |
[PATCH] Fix 32bit sendmsg() flaw
When we copy 32bit ->msg_control contents to kernel, we walk the same
userland data twice without sanity checks on the second pass.
Second version of this patch: the original broke with 64-bit arches
running 32-bit-compat-mode executables doing sendmsg() syscalls with
unaligned CMSG data areas
Another thing is that we use kmalloc() to allocate and sock_kfree_s()
to free afterwards; less serious, but also needs fixing.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Chris Wright <chrisw@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'net/compat.c')
-rw-r--r-- | net/compat.c | 44 |
1 files changed, 26 insertions, 18 deletions
diff --git a/net/compat.c b/net/compat.c index d99ab9695893..e593dace2fdb 100644 --- a/net/compat.c +++ b/net/compat.c | |||
@@ -135,13 +135,14 @@ static inline struct compat_cmsghdr __user *cmsg_compat_nxthdr(struct msghdr *ms | |||
135 | * thus placement) of cmsg headers and length are different for | 135 | * thus placement) of cmsg headers and length are different for |
136 | * 32-bit apps. -DaveM | 136 | * 32-bit apps. -DaveM |
137 | */ | 137 | */ |
138 | int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, | 138 | int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, struct sock *sk, |
139 | unsigned char *stackbuf, int stackbuf_size) | 139 | unsigned char *stackbuf, int stackbuf_size) |
140 | { | 140 | { |
141 | struct compat_cmsghdr __user *ucmsg; | 141 | struct compat_cmsghdr __user *ucmsg; |
142 | struct cmsghdr *kcmsg, *kcmsg_base; | 142 | struct cmsghdr *kcmsg, *kcmsg_base; |
143 | compat_size_t ucmlen; | 143 | compat_size_t ucmlen; |
144 | __kernel_size_t kcmlen, tmp; | 144 | __kernel_size_t kcmlen, tmp; |
145 | int err = -EFAULT; | ||
145 | 146 | ||
146 | kcmlen = 0; | 147 | kcmlen = 0; |
147 | kcmsg_base = kcmsg = (struct cmsghdr *)stackbuf; | 148 | kcmsg_base = kcmsg = (struct cmsghdr *)stackbuf; |
@@ -156,6 +157,7 @@ int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, | |||
156 | 157 | ||
157 | tmp = ((ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))) + | 158 | tmp = ((ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))) + |
158 | CMSG_ALIGN(sizeof(struct cmsghdr))); | 159 | CMSG_ALIGN(sizeof(struct cmsghdr))); |
160 | tmp = CMSG_ALIGN(tmp); | ||
159 | kcmlen += tmp; | 161 | kcmlen += tmp; |
160 | ucmsg = cmsg_compat_nxthdr(kmsg, ucmsg, ucmlen); | 162 | ucmsg = cmsg_compat_nxthdr(kmsg, ucmsg, ucmlen); |
161 | } | 163 | } |
@@ -167,30 +169,34 @@ int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, | |||
167 | * until we have successfully copied over all of the data | 169 | * until we have successfully copied over all of the data |
168 | * from the user. | 170 | * from the user. |
169 | */ | 171 | */ |
170 | if(kcmlen > stackbuf_size) | 172 | if (kcmlen > stackbuf_size) |
171 | kcmsg_base = kcmsg = kmalloc(kcmlen, GFP_KERNEL); | 173 | kcmsg_base = kcmsg = sock_kmalloc(sk, kcmlen, GFP_KERNEL); |
172 | if(kcmsg == NULL) | 174 | if (kcmsg == NULL) |
173 | return -ENOBUFS; | 175 | return -ENOBUFS; |
174 | 176 | ||
175 | /* Now copy them over neatly. */ | 177 | /* Now copy them over neatly. */ |
176 | memset(kcmsg, 0, kcmlen); | 178 | memset(kcmsg, 0, kcmlen); |
177 | ucmsg = CMSG_COMPAT_FIRSTHDR(kmsg); | 179 | ucmsg = CMSG_COMPAT_FIRSTHDR(kmsg); |
178 | while(ucmsg != NULL) { | 180 | while(ucmsg != NULL) { |
179 | __get_user(ucmlen, &ucmsg->cmsg_len); | 181 | if (__get_user(ucmlen, &ucmsg->cmsg_len)) |
182 | goto Efault; | ||
183 | if (!CMSG_COMPAT_OK(ucmlen, ucmsg, kmsg)) | ||
184 | goto Einval; | ||
180 | tmp = ((ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))) + | 185 | tmp = ((ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))) + |
181 | CMSG_ALIGN(sizeof(struct cmsghdr))); | 186 | CMSG_ALIGN(sizeof(struct cmsghdr))); |
187 | if ((char *)kcmsg_base + kcmlen - (char *)kcmsg < CMSG_ALIGN(tmp)) | ||
188 | goto Einval; | ||
182 | kcmsg->cmsg_len = tmp; | 189 | kcmsg->cmsg_len = tmp; |
183 | __get_user(kcmsg->cmsg_level, &ucmsg->cmsg_level); | 190 | tmp = CMSG_ALIGN(tmp); |
184 | __get_user(kcmsg->cmsg_type, &ucmsg->cmsg_type); | 191 | if (__get_user(kcmsg->cmsg_level, &ucmsg->cmsg_level) || |
185 | 192 | __get_user(kcmsg->cmsg_type, &ucmsg->cmsg_type) || | |
186 | /* Copy over the data. */ | 193 | copy_from_user(CMSG_DATA(kcmsg), |
187 | if(copy_from_user(CMSG_DATA(kcmsg), | 194 | CMSG_COMPAT_DATA(ucmsg), |
188 | CMSG_COMPAT_DATA(ucmsg), | 195 | (ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))))) |
189 | (ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))))) | 196 | goto Efault; |
190 | goto out_free_efault; | ||
191 | 197 | ||
192 | /* Advance. */ | 198 | /* Advance. */ |
193 | kcmsg = (struct cmsghdr *)((char *)kcmsg + CMSG_ALIGN(tmp)); | 199 | kcmsg = (struct cmsghdr *)((char *)kcmsg + tmp); |
194 | ucmsg = cmsg_compat_nxthdr(kmsg, ucmsg, ucmlen); | 200 | ucmsg = cmsg_compat_nxthdr(kmsg, ucmsg, ucmlen); |
195 | } | 201 | } |
196 | 202 | ||
@@ -199,10 +205,12 @@ int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, | |||
199 | kmsg->msg_controllen = kcmlen; | 205 | kmsg->msg_controllen = kcmlen; |
200 | return 0; | 206 | return 0; |
201 | 207 | ||
202 | out_free_efault: | 208 | Einval: |
203 | if(kcmsg_base != (struct cmsghdr *)stackbuf) | 209 | err = -EINVAL; |
204 | kfree(kcmsg_base); | 210 | Efault: |
205 | return -EFAULT; | 211 | if (kcmsg_base != (struct cmsghdr *)stackbuf) |
212 | sock_kfree_s(sk, kcmsg_base, kcmlen); | ||
213 | return err; | ||
206 | } | 214 | } |
207 | 215 | ||
208 | int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data) | 216 | int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data) |