aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2005-09-07 21:28:51 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2005-09-08 11:14:11 -0400
commit8920e8f94c44e31a73bdf923b04721e26e88cadd (patch)
tree7a0195643c37c63335224358256fab8cd445a671
parent5aa3b610a7330c3cd6f0cb264d2189a3a1dcf534 (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>
-rw-r--r--include/net/compat.h2
-rw-r--r--net/compat.c44
-rw-r--r--net/socket.c3
3 files changed, 29 insertions, 20 deletions
diff --git a/include/net/compat.h b/include/net/compat.h
index 9983fd857804..482eb820f13a 100644
--- a/include/net/compat.h
+++ b/include/net/compat.h
@@ -33,7 +33,7 @@ extern asmlinkage long compat_sys_sendmsg(int,struct compat_msghdr __user *,unsi
33extern asmlinkage long compat_sys_recvmsg(int,struct compat_msghdr __user *,unsigned); 33extern asmlinkage long compat_sys_recvmsg(int,struct compat_msghdr __user *,unsigned);
34extern asmlinkage long compat_sys_getsockopt(int, int, int, char __user *, int __user *); 34extern asmlinkage long compat_sys_getsockopt(int, int, int, char __user *, int __user *);
35extern int put_cmsg_compat(struct msghdr*, int, int, int, void *); 35extern int put_cmsg_compat(struct msghdr*, int, int, int, void *);
36extern int cmsghdr_from_user_compat_to_kern(struct msghdr *, unsigned char *, 36extern int cmsghdr_from_user_compat_to_kern(struct msghdr *, struct sock *, unsigned char *,
37 int); 37 int);
38 38
39#endif /* NET_COMPAT_H */ 39#endif /* NET_COMPAT_H */
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 */
138int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, 138int 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
202out_free_efault: 208Einval:
203 if(kcmsg_base != (struct cmsghdr *)stackbuf) 209 err = -EINVAL;
204 kfree(kcmsg_base); 210Efault:
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
208int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data) 216int put_cmsg_compat(struct msghdr *kmsg, int level, int type, int len, void *data)
diff --git a/net/socket.c b/net/socket.c
index e1bd5d84d7bf..c699e93c33d7 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1745,10 +1745,11 @@ asmlinkage long sys_sendmsg(int fd, struct msghdr __user *msg, unsigned flags)
1745 goto out_freeiov; 1745 goto out_freeiov;
1746 ctl_len = msg_sys.msg_controllen; 1746 ctl_len = msg_sys.msg_controllen;
1747 if ((MSG_CMSG_COMPAT & flags) && ctl_len) { 1747 if ((MSG_CMSG_COMPAT & flags) && ctl_len) {
1748 err = cmsghdr_from_user_compat_to_kern(&msg_sys, ctl, sizeof(ctl)); 1748 err = cmsghdr_from_user_compat_to_kern(&msg_sys, sock->sk, ctl, sizeof(ctl));
1749 if (err) 1749 if (err)
1750 goto out_freeiov; 1750 goto out_freeiov;
1751 ctl_buf = msg_sys.msg_control; 1751 ctl_buf = msg_sys.msg_control;
1752 ctl_len = msg_sys.msg_controllen;
1752 } else if (ctl_len) { 1753 } else if (ctl_len) {
1753 if (ctl_len > sizeof(ctl)) 1754 if (ctl_len > sizeof(ctl))
1754 { 1755 {