编写安全代码——sendto和recvfrom的坑

编写安全代码——sendto和recvfrom的坑

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710

近日帮一个兄弟查代码问题,再处理完一系列问题以后,发现程序某些时候工作还是不正常,甚至会崩溃。因为环境所限,不能使用gdb,所以我只能review他的代码。最终发现原来是sendto和recvfrom挖的坑。

让我们看一下sendto和recvfrom的原型:

 #include <sys/types.h>
   #include <sys/socket.h>

   ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen); 

众所周知,对于TCP/IP的socket来说,也就是AF_INET的socket,其地址类型应该为struct sockaddr_in,在调用sendto和recvfrom不得不进行强制类型转换——万恶的强制类型转换!!!代码要写成这样:

sendto(sock, buf, len, (const struct sockaddr *)&dest_addr, sizeof(dest_addr)); 

而这次的bug就是由于这个强制类型转换造成的。

原来的代码大致如下:

int send_func(int sock, const char *buf, int len)
{
    struct sockaddr_in dst;
    /* 省略初始化dst */
    /* 省略其它业务逻辑 */
    return sendto(sock, buf, len, 0, (const struct sockaddr*)&dst, sizeof(struct sockaddr_in));
} 

对代码进行了重构,目的地址变为了参数,结果代码变成了下面这个样子:

int send_func(int sock, const char *buf, int len, struct sockaddr_in *dst)
{
    /* 省略其它业务逻辑 */
    return sendto(sock, buf, len, 0, (const struct sockaddr*)&dst, sizeof(struct sockaddr_in));
} 

错误就在于dst从原来的struct sockaddr_in类型,变为了struct sockaddr_in指针,但是sendto的语句却忘了做相应的改动,这样sendto写入到了错误的地址&dst,导致内存错误的发生。这个bug在当前的示例中,好像很容易找到,但是在真正的工程中,在大量的代码中找到这个问题就不是那么容易的事情了。

那么我们如何避免这样类似的bug呢?从这个教训中,我们要得到经验——强制类型转换是万恶之源。为什么要强制转换呢?肯定是因为你的代码不满足被调用者的要求,这已经意味着危险了。另外,由于使用了强制类型转换,这意味着gcc编译器无法发现类型不匹配的错误,这也是上面例子中,问题没有在编译阶段发现的原因。

这样的话,我们就要问一下,为什么sendto和recvfrom要这样设计呢?这是因为sendto和recvfrom面对的所有类型的socket。因此地址不仅仅是AF_INET的地址类型struct sockaddr_in,比如对于Unix域socket,地址类型则是struct sockaddr_un。面对这么多不同地址类型,C本身又不支持函数重载,因此只能定义一个“通用”的结构。

最后如何来避免这种类似问题呢?我们无法改变sendto的用法,但是可以通过封装来解决这一问题——封装本来就是用于隔离变化,隔离问题的手段。

static inline int inet_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_in *dest_addr)
{
    return sendto(sockfd, buf, len, flags, (const struct sockaddr*)dest_addr, sizeof(*dest_addr));
}

static inline int un_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_un *dest_addr)
{
    return sendto(sockfd, buf, len, flags, (const struct sockaddr*)dest_addr, sizeof(*dest_addr));
} 

通过定义inline函数,来封装强制类型转换,对于每一种具体的socket,都调用封装的inline函数,虽然看上去多写了几行代码,但却是一劳永逸。

将问题或者潜在的问题消除,才能真正提高工作效率和产品质量。

阅读(380) | 评论(0) | 转发(0) |
评论热议
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值