CVE-2024-22857: Arbitrary Code Execution in zlog
by Ali Raza
Vulnerability
Heap-based buffer flow in zlog
versions v1.1.0
to v1.2.17
in zlog_rule_new()
. The size of record_name
is MAXLEN_PATH(1024) + 1, but file_path
may have data up to MAXLEN_CFG_LINE(MAXLEN_PATH*4) + 1. A check was missing in zlog_rule_new()
when 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 achieve arbitrary code execution or potentially cause remote code execution (RCE).
Now, let’s take a deeper look at the zlog library, and then we will see how to exploit this vulnerability.
zlog Library
Quoted from the zlog User’s Guide
zlog
is a reliable, high-performance, thread-safe, flexible, clear-model, pure C logging library. In the C world, there was no good logging library for applications like Logback in Java or Log4cxx in C++. Usingprintf
can work, but it cannot be easily redirected or reformatted.syslog
is slow and designed for system use. So, zlog was created. It is faster, safer, and more powerful thanlog4c
, 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 not to install any target on my host machine, so I will use the compiled library from the source directory.
How to use
Zlog is a simple but highly customizable logging library. You can use it in your application by crafting a configuration file for your needs and then using the library functions to log messages. Detailed documentation is available in 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
:
- Categories
They specify different kinds of log entries. In the zlog source code, a category is a (zlog_category_t *
) variable.
- Formats
They describe log patterns, such as with or without timestamps, source files, and source lines.
- Rules
Rules consist of a 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. However, there is a complex match range of categories. The rule decouples variable conditions.
Let’s look at 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 follows:
$ ./main
Hello, zlog
This displays the log on stdout
, as defined in the configuration file. zlog supports various output methods. The syntax is as follows:
(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 the user-defined output. We use the following template to define a 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 configuration will be:
[formats]
simple = "%m%n"
[rules]
mycat.* $my_func, "~/zlog_out.txt"; simple
Exploitation
The vulnerability is triggered when 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 with the exploit code, on my company’s (Ebryx) blog. You can read the full post here.
Timeline
- 11-22-2023: Vulnerability discovered and reported to the vendor.
- 01-10-2024: Reported to MITRE, and a CVE number was reserved.
- 03-07-2024: Public disclosure and CVE published.
References
- https://www.ebryx.com/blogs/arbitrary-code-execution-in-zlog-cve-2024-22857
- https://www.cybersecurity-help.cz/vdb/SB2024022842
- https://www.openwall.com/lists/oss-security/2024/02/28/8
- https://github.com/HardySimpson/zlog/pull/251
— locus_x64
tags: Vulnerability Research - Exploitation