Microsoft has discovered a set of memory corruption vulnerabilities in a library called ncurses, which provides APIs that support text-based user interfaces (TUI). Released in 1993, the ncurses library is commonly used by various programs on Portable Operating System Interface (POSIX) operating systems, including Linux, macOS, and FreeBSD. Using environment variable poisoning, attackers could chain these vulnerabilities to elevate privileges and run code in the targeted program’s context or perform other malicious actions.
One of the most common vulnerabilities found in modern software, memory corruption vulnerabilities, can allow attackers to gain unauthorized access to systems and data by modifying a program’s memory. The impact of memory corruption vulnerabilities can range from leaking sensitive information and performing a simple denial-of-service (DoS) to elevating privileges and executing arbitrary code.
Microsoft has shared these vulnerabilities with the relevant maintainers through Coordinated Vulnerability Disclosure (CVD) via Microsoft Security Vulnerability Research (MSVR). Fixes for these vulnerabilities, now identified as CVE-2023-29491 with a CVSS score of 7.8, have been successfully deployed by the maintainers of the ncurses library, Thomas E. Dickey, in commit 20230408. We wish to thank Thomas for his professionalism and collaboration in resolving those issues. We also worked with Apple on addressing the macOS-specific issues related to these vulnerabilities, and we thank Apple for their response and partnership. Lastly, during our analysis, a researcher named Gergely Kalman engaged us privately over Twitter and contributed relevant use cases in addition to his own hand-coded fuzzer. We thank Gergely for his contributions in advancing this research and community engagement. Users of ncurses are encouraged to update their instances and systems.
In this blog post, we share information about ncurses and the discovered memory corruption vulnerabilities. We also share this research to emphasize the importance of collaboration among researchers, industry partners, and the larger security community in the effort to improve security for all.
Terminal databases are used by ncurses to be terminal-independent, meaning the capabilities of the terminal are not required to be known ahead-of-time. Terminal databases contain a set of capabilities that ultimately determine the control characters that are sent to the terminal (instructing the terminal to perform basic interactions) and describe various properties of the terminal. Terminal databases come in two major formats: the older and less commonly used termcap (terminal capability) format, and the improved terminfo format. Since terminals can differ on the types of control characters they expect and the operations they support, terminfo became necessary to address this discrepancy. In its textual syntax, capabilities are separated by commas, and come in three forms:
- Boolean capabilities: for example, the am capability specifies that the terminal supports automatic margins. In the terminfo textual syntax, Boolean capabilities appear by their name alone, without any additions.
- Numeric capabilities: for instance, the cols capability contains the number of columns in a line. In the terminfo textual syntax, numeric capabilities are recognized with a “#” symbol after their name, followed by the numeric value, such as “cols#80”.
- String capabilities: for instance, the clear capability describes the control character that should be transmitted to the terminal to clear the screen. In the terminfo textual syntax, string capabilities are recognized with a “=” symbol after their name, followed by the string value, such as “clear=E[HE[2J”.
POSIX systems usually pre-ship with tens of such databases. It’s possible to parse the capabilities of the current database with the infocmp utility:

Every modern operating system contains a set of environment variables that might affect the behavior of programs. A well-known technique for attackers is to manipulate those environment variables to cause programs to perform actions that would benefit their malicious purposes, hence “poisoning” them. There have been multiple cases of environment variable poisoning in the past, for instance:
- CVE-2023-22809: users were allowed to elevate their privileges by poisoning the EDITOR environment variable (and similar other environment variables) and running sudoedit, which ultimately allowed them to edit arbitrary files.
- CVE-2022-0563: the environment variable INPUTRC is indirectly used by the chsh and chfn set-UID Linux binaries. It was discovered that INPUTRC could be poisoned to dump the contents of sensitive files on the system.
- CVE-2020-9934: the HOME environment variable could be poisoned to bypass Transparency, Consent, and Control (TCC) on macOS, thus gaining access to otherwise inaccessible sensitive data. We have found a similar bypass and reported it in 2021.
- CVE–2023-32369: the PERL5OPT and BASH_ENV environment variables could be poisoned to bypass System Integrity Protection (SIP) in macOS, thus elevating privileges. We have reported the vulnerability in April 2023.
- The LD_PRELOAD environment variable is commonly used in Linux for code injection purposes.
- The WINDIR and SYSTEMROOT environment variables have been used in the past on Windows for bypassing User Account Control (UAC).
We have discovered that during initialization, the ncurses library searches for several environment variables, including an environment variable similarly named TERMINFO. When using terminfo databases, the program consults a fixed directory path unless a TERMINFO environment variable is present, which instead points the program to an alternative directory that contains compiled terminfo database files. Moreover, there are interesting common programs that use ncurses, most notably top on macOS, which is a set-UID binary (which runs with elevated privileges) that also uses the TERMINFO environment variable. Therefore, finding vulnerabilities in ncurses have the potential to affect many programs and possibly elevate privileges. It’s noteworthy that the potential of poisoning the TERMINFO environment variable was highlighted several times in the past (for example, here), but we have not seen comprehensive research on the topic of terminfo capabilities for offensive security purposes.
For completeness, while this blog post focuses on how attackers could poison the TERMINFO environment variable to potentially exploit ncurses vulnerabilities, the HOME environment variable could have been similarly manipulated. Assuming the TERMINFO environment variable was never defined, ncurses looks for a $HOME/.terminfo directory. This could have been abused by planting a .terminfo directory at an arbitrary path and poisoning the HOME environment variable, so the technique is quite similar.
The terminfo capabilities are richer than they first appear. In a nutshell, capabilities are allowed to receive up to nine parameters (p1-p9) and use them in a stack data structure. Furthermore, capabilities work with a stack-like structure and instructions that can push (place an item in the stack) and pop (get an item from the stack) data, perform logical-arithmetic operations, and even support conditions. Here are some examples:
Operation | Description |
%{number} | Push a constant value to the stack. |
%px | Push the parameter to the stack. |
%+, %-, %*, %/, %m | Pop two numbers from the stack and push the arithmetic result of the stack. Addition, substruction, multiplication, division, and remainder operations are supported. |
%&, %|, %^ | Pop two numbers from the stack and push the bitwise result to the stack. Bitwise OR, AND, and XOR are supported. |
%=, %<, %>, %A, %O | Pop two numbers and compare them, pushing the logical result back to the stack. The operations of comparison, less-than, and greater-than are supported, as well as logical AND and OR operations. |
%l | Pop a string from the stack and push its length back to the stack. |
%?[condition]%t[body1]%e[body2]%; | Perform a condition. The %t operation pops a numeric value from the stack and compares it to 0. The result determines what body to execute (the “else” body is optional and comes after the %e delimiter). |
%s, %c | Pop a string from the stack and print it out to the terminal. |
%d, %x | Pop a number from the stack and print it out to the terminal. |
While not Turing-complete, terminfo offers functionality that resembles very basic