2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-21 11:44:01 +08:00
linux-next/fs/smbfs/sock.c
Jeff Layton ce88cc5ed8 smbfs: fix calculation of kernel_recvmsg size parameter in smb_receive()
smb_receive calls kernel_recvmsg with a size that's the minimum of the
amount of buffer space in the kvec passed in or req->rq_rlen (which
represents the length of the response).  This does not take into account
any data that was read in a request earlier pass through smb_receive.

If the first pass through smb_receive receives some but not all of the
response, then the next pass can call kernel_recvmsg with a size field
that's too big.  kernel_recvmsg can overrun into the next response,
throwing off the alignment and making it unrecognizable.

This causes messages like this to pop up in the ring buffer:

smb_get_length: Invalid NBT packet, code=69

as well as other errors indicating that the response is unrecognizable.
Typically this is seen on a smbfs mount under heavy I/O.

This patch changes the code to use (req->rq_rlen - req->rq_bytes_recvd)
instead instead of just req->rq_rlen, since that should represent the
amount of unread data in the response.

I think this is correct, but an ACK or NACK from someone more familiar
with this code would be appreciated...

Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2008-02-06 10:41:02 -08:00

387 lines
7.8 KiB
C

/*
* sock.c
*
* Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke
* Copyright (C) 1997 by Volker Lendecke
*
* Please add a note about your changes to smbfs in the ChangeLog file.
*/
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <linux/socket.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/mm.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <net/scm.h>
#include <net/tcp_states.h>
#include <net/ip.h>
#include <linux/smb_fs.h>
#include <linux/smb.h>
#include <linux/smbno.h>
#include <asm/uaccess.h>
#include <asm/ioctls.h>
#include "smb_debug.h"
#include "proto.h"
#include "request.h"
static int
_recvfrom(struct socket *socket, unsigned char *ubuf, int size, unsigned flags)
{
struct kvec iov = {ubuf, size};
struct msghdr msg = {.msg_flags = flags};
msg.msg_flags |= MSG_DONTWAIT | MSG_NOSIGNAL;
return kernel_recvmsg(socket, &msg, &iov, 1, size, msg.msg_flags);
}
/*
* Return the server this socket belongs to
*/
static struct smb_sb_info *
server_from_socket(struct socket *socket)
{
return socket->sk->sk_user_data;
}
/*
* Called when there is data on the socket.
*/
void
smb_data_ready(struct sock *sk, int len)
{
struct smb_sb_info *server = server_from_socket(sk->sk_socket);
void (*data_ready)(struct sock *, int) = server->data_ready;
data_ready(sk, len);
VERBOSE("(%p, %d)\n", sk, len);
smbiod_wake_up();
}
int
smb_valid_socket(struct inode * inode)
{
return (inode && S_ISSOCK(inode->i_mode) &&
SOCKET_I(inode)->type == SOCK_STREAM);
}
static struct socket *
server_sock(struct smb_sb_info *server)
{
struct file *file;
if (server && (file = server->sock_file))
{
#ifdef SMBFS_PARANOIA
if (!smb_valid_socket(file->f_path.dentry->d_inode))
PARANOIA("bad socket!\n");
#endif
return SOCKET_I(file->f_path.dentry->d_inode);
}
return NULL;
}
void
smb_close_socket(struct smb_sb_info *server)
{
struct file * file = server->sock_file;
if (file) {
struct socket *sock = server_sock(server);
VERBOSE("closing socket %p\n", sock);
sock->sk->sk_data_ready = server->data_ready;
server->sock_file = NULL;
fput(file);
}
}
static int
smb_get_length(struct socket *socket, unsigned char *header)
{
int result;
result = _recvfrom(socket, header, 4, MSG_PEEK);
if (result == -EAGAIN)
return -ENODATA;
if (result < 0) {
PARANOIA("recv error = %d\n", -result);
return result;
}
if (result < 4)
return -ENODATA;
switch (header[0]) {
case 0x00:
case 0x82:
break;
case 0x85:
DEBUG1("Got SESSION KEEP ALIVE\n");
_recvfrom(socket, header, 4, 0); /* read away */
return -ENODATA;
default:
PARANOIA("Invalid NBT packet, code=%x\n", header[0]);
return -EIO;
}
/* The length in the RFC NB header is the raw data length */
return smb_len(header);
}
int
smb_recv_available(struct smb_sb_info *server)
{
mm_segment_t oldfs;
int avail, err;
struct socket *sock = server_sock(server);
oldfs = get_fs();
set_fs(get_ds());
err = sock->ops->ioctl(sock, SIOCINQ, (unsigned long) &avail);
set_fs(oldfs);
return (err >= 0) ? avail : err;
}
/*
* Adjust the kvec to move on 'n' bytes (from nfs/sunrpc)
*/
static int
smb_move_iov(struct kvec **data, size_t *num, struct kvec *vec, unsigned amount)
{
struct kvec *iv = *data;
int i;
int len;
/*
* Eat any sent kvecs
*/
while (iv->iov_len <= amount) {
amount -= iv->iov_len;
iv++;
(*num)--;
}
/*
* And chew down the partial one
*/
vec[0].iov_len = iv->iov_len-amount;
vec[0].iov_base =((unsigned char *)iv->iov_base)+amount;
iv++;
len = vec[0].iov_len;
/*
* And copy any others
*/
for (i = 1; i < *num; i++) {
vec[i] = *iv++;
len += vec[i].iov_len;
}
*data = vec;
return len;
}
/*
* smb_receive_header
* Only called by the smbiod thread.
*/
int
smb_receive_header(struct smb_sb_info *server)
{
struct socket *sock;
int result = 0;
unsigned char peek_buf[4];
result = -EIO;
sock = server_sock(server);
if (!sock)
goto out;
if (sock->sk->sk_state != TCP_ESTABLISHED)
goto out;
if (!server->smb_read) {
result = smb_get_length(sock, peek_buf);
if (result < 0) {
if (result == -ENODATA)
result = 0;
goto out;
}
server->smb_len = result + 4;
if (server->smb_len < SMB_HEADER_LEN) {
PARANOIA("short packet: %d\n", result);
server->rstate = SMB_RECV_DROP;
result = -EIO;
goto out;
}
if (server->smb_len > SMB_MAX_PACKET_SIZE) {
PARANOIA("long packet: %d\n", result);
server->rstate = SMB_RECV_DROP;
result = -EIO;
goto out;
}
}
result = _recvfrom(sock, server->header + server->smb_read,
SMB_HEADER_LEN - server->smb_read, 0);
VERBOSE("_recvfrom: %d\n", result);
if (result < 0) {
VERBOSE("receive error: %d\n", result);
goto out;
}
server->smb_read += result;
if (server->smb_read == SMB_HEADER_LEN)
server->rstate = SMB_RECV_HCOMPLETE;
out:
return result;
}
static char drop_buffer[PAGE_SIZE];
/*
* smb_receive_drop - read and throw away the data
* Only called by the smbiod thread.
*
* FIXME: we are in the kernel, could we just tell the socket that we want
* to drop stuff from the buffer?
*/
int
smb_receive_drop(struct smb_sb_info *server)
{
struct socket *sock;
unsigned int flags;
struct kvec iov;
struct msghdr msg;
int rlen = smb_len(server->header) - server->smb_read + 4;
int result = -EIO;
if (rlen > PAGE_SIZE)
rlen = PAGE_SIZE;
sock = server_sock(server);
if (!sock)
goto out;
if (sock->sk->sk_state != TCP_ESTABLISHED)
goto out;
flags = MSG_DONTWAIT | MSG_NOSIGNAL;
iov.iov_base = drop_buffer;
iov.iov_len = PAGE_SIZE;
msg.msg_flags = flags;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = NULL;
result = kernel_recvmsg(sock, &msg, &iov, 1, rlen, flags);
VERBOSE("read: %d\n", result);
if (result < 0) {
VERBOSE("receive error: %d\n", result);
goto out;
}
server->smb_read += result;
if (server->smb_read >= server->smb_len)
server->rstate = SMB_RECV_END;
out:
return result;
}
/*
* smb_receive
* Only called by the smbiod thread.
*/
int
smb_receive(struct smb_sb_info *server, struct smb_request *req)
{
struct socket *sock;
unsigned int flags;
struct kvec iov[4];
struct kvec *p = req->rq_iov;
size_t num = req->rq_iovlen;
struct msghdr msg;
int rlen;
int result = -EIO;
sock = server_sock(server);
if (!sock)
goto out;
if (sock->sk->sk_state != TCP_ESTABLISHED)
goto out;
flags = MSG_DONTWAIT | MSG_NOSIGNAL;
msg.msg_flags = flags;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = NULL;
/* Dont repeat bytes and count available bufferspace */
rlen = min_t(int, smb_move_iov(&p, &num, iov, req->rq_bytes_recvd),
(req->rq_rlen - req->rq_bytes_recvd));
result = kernel_recvmsg(sock, &msg, p, num, rlen, flags);
VERBOSE("read: %d\n", result);
if (result < 0) {
VERBOSE("receive error: %d\n", result);
goto out;
}
req->rq_bytes_recvd += result;
server->smb_read += result;
out:
return result;
}
/*
* Try to send a SMB request. This may return after sending only parts of the
* request. SMB_REQ_TRANSMITTED will be set if a request was fully sent.
*
* Parts of this was taken from xprt_sendmsg from net/sunrpc/xprt.c
*/
int
smb_send_request(struct smb_request *req)
{
struct smb_sb_info *server = req->rq_server;
struct socket *sock;
struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
int slen = req->rq_slen - req->rq_bytes_sent;
int result = -EIO;
struct kvec iov[4];
struct kvec *p = req->rq_iov;
size_t num = req->rq_iovlen;
sock = server_sock(server);
if (!sock)
goto out;
if (sock->sk->sk_state != TCP_ESTABLISHED)
goto out;
/* Dont repeat bytes */
if (req->rq_bytes_sent)
smb_move_iov(&p, &num, iov, req->rq_bytes_sent);
result = kernel_sendmsg(sock, &msg, p, num, slen);
if (result >= 0) {
req->rq_bytes_sent += result;
if (req->rq_bytes_sent >= req->rq_slen)
req->rq_flags |= SMB_REQ_TRANSMITTED;
}
out:
return result;
}