ali@pwnworld$

Exploring kernel exploitation.

9 March 2024

CVE-2024-22857: Arbitrary Code Execution in zlog

by Ali Raza

CVE-2024-22857

Vulnerability

Heap based buffer flow in zlog v1.1.0 to v1.2.17 in zlog_rule_new(). Size of record_name is MAXLEN_PATH(1024) + 1 but file_path may have data upto MAXLEN_CFG_LINE(MAXLEN_PATH*4) + 1. So a check was missing in zlog_rule_new() while copying the record_name from file_path + 1 which caused the buffer overflow. An attacker can exploit this vulnerability to overwrite the zlog_record_fn record_func function pointer to get arbitrary code execution or potentially cause remote code execution (RCE).

Now let’s take a deeper look into the zlog library and then we will see how we can exploit this vulnerability.

zlog Library

Quoted from zlog User’s Guide

zlog is a reliable, high-performance, thread safe, flexible, clear-model, pure C logging library. Actually, in the C world there was NO good logging library for applications like logback in java or log4cxx in c++. Using printf can work, but can not be redirected or reformatted easily. syslog is slow and is designed for system use. So zlog is there. It is faster, safer and more powerful than log4c. So it can be widely used.

Download and Build zlog

You can download the zlog library from github (https://github.com/HardySimpson/zlog/). After downloading the library, you can build it using the following commands:

cd zlog
make

I prefer to not install any target to my host machine, so I will use the compiled library from the source directory.

How to use

Zlog is is a simple but more customizable logging library. You can use it in your application by simply crafting a configuration file of your need and then using the library functions to log the messages. A detailed documentation is available on the zlog User’s Guide. I will explain a few important aspects of the library here.

Configure

zlog uses a user-provided configuration file to initialize the logging objects. It has a specific format. There are 3 important concepts in zlog:

They specify different kinds of log entries. In the zlog source code, category is a (zlog_cateogory_t *) variable.

They describe log patterns, such as: with or without time stamps, source files, and source lines.

Rules consist of category, level, output file (or other channel), and format. In brief, if the category string in a rule in the configuration file equals the name of a category variable in the source, then they match. Still, there is a complex match range of categories. Rule decouples variable conditions.

Let’s have a “Hello, Zlog” program that uses zlog. Config file:

[formats]
simple = "%m%n"
[rules]
mycat.INFO >stdout; simple

Driver Program:

int main(){
    const char * config_file = "zlog.conf";
    int rv = zlog_init(config_file);
    if (rv){
        printf("init failed\\n");
        return -1;
    }
    zlog_category_t *zc;
    zc = zlog_get_category("mycat");
    if (!zc) {
        printf("get cat fail\\n");
        zlog_fini();
        return -2;
    }
    zlog_info(c, "Hello, zlog");
    zlog_fini();
    return 0;
}

This will simply display the info log as:

$ ./main
Hello, zlog

This displays the log on stdout as defined in the configuration file. zlog supports various output methods. The syntax is: (output action), (output option); (format name, optional)

Output Output Action Output Option  
to standard out >stdout no meaning  
to standard error >stderr no meaning  
to syslog >syslog syslog facility, can be: LOG_USER(default), LOG_LOCAL[0-7] This is required.  
pipeline output   cat no meaning
to file “(file path)” rotation. see 5.6 for detail 10MB * 3 ~ “press.#r.log”  
synchronous I/O file -“(file path)”    
user-defined output $name “path” (dynamic or static) of record function  

Our interest is in user-defined output. We use the following template to define user-defined output.

int in_program_func(zlog_msg_t *msg){
    // Do whatever you want to, with the output
}

int main(void){
    int rc = zlog_init("zlog.conf");
    if (rc){
        printf("init failed\\n");
        return -1;
    }
    zlog_category_t *zc = zlog_get_category("mycat");
    // set in_program_func as output function
    if(zlog_set_record("my_func", in_program_func) != 0){ // [1]
        printf("set record func failed\\n");
        zlog_fini();
        return -2;
    }
    zlog_info(zc, "Hello, zlog"); // [2]
    zlog_fini();
    return 0;
}

and the related config will be:

[formats]
simple = "%m%n"
[rules]
mycat.* $my_func, "~/zlog_out.txt"; simple

Exploitation

The vulnerability is triggered while parsing the configuration file. The zlog_rule_new() function is responsible for parsing the configuration file and creating a new rule.

I have posted a detailed explanation of the vulnerability along the exploit code on my company’s (Ebryx) blog. You can read the full post here.

Timeline

References

~Ali Raza Mumtaz (arm)

tags: Vulnerability Research - Exploitation