kvspb / nginx-auth-ldap

LDAP authentication module for nginx
BSD 2-Clause "Simplified" License
735 stars 252 forks source link

ldap segfault #143

Open quenode opened 8 years ago

quenode commented 8 years ago

ubuntu 14.04 nginx 1.10

Crashes when having to much ldap server in virtual hosts.

this what I use

ldap_server coa {
        url ldaps://ipa.domain.com/DC=domain,DC=com?uid?sub?(&(memberOf=cn=coa,cn=groups,cn=accounts,dc=domain,dc=com)(objectClass=person));
        binddn "uid=ldap_proxy,cn=users,cn=compat,dc=domain,dc=com";
        binddn_passwd "paaass";
        require valid_user;
}

Free IPA LDAP


[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `nginx: worker process                           '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  ngx_http_auth_ldap_get_connection (ctx=ctx@entry=0x7f1b05443bd8) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:1086
1086            ngx_queue_remove(q);

0  ngx_http_auth_ldap_get_connection (ctx=ctx@entry=0x7f1b05443bd8) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:1086
        server = 0x7f1b050649b0
        q = 0x0
#1  0x00007f1b04c49945 in ngx_http_auth_ldap_get_connection (ctx=0x7f1b05443bd8) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:2145
No locals.
#2  ngx_http_auth_ldap_search (ctx=0x7f1b05443bd8, r=0x7f1b0542a910) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:1936
        ludpp = <optimized out>
        filter = <optimized out>
        attrs = {0xa9e64845dd273c9f <error: Cannot access memory at address 0xa9e64845dd273c9f>, 0xe1070f78631b8787 <error: Cannot access memory at address 0xe1070f78631b8787>}
        rc = <optimized out>
#3  ngx_http_auth_ldap_authenticate (conf=<optimized out>, ctx=0x7f1b05443bd8, r=<optimized out>) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:1790
No locals.
#4  ngx_http_auth_ldap_handler (r=<optimized out>) at /root/nginx_1_10/nginx-1.10.0/debian/modules/nginx-auth-ldap/ngx_http_auth_ldap_module.c:1717
        alcf = <optimized out>
        ctx = 0x7f1b05443bd8
        rc = <optimized out>
#5  0x00007f1b04bcbce4 in ngx_http_core_access_phase (r=<optimized out>, ph=0x7f1b054846c0) at src/http/ngx_http_core_module.c:1073
        rc = <optimized out>
        clcf = <optimized out>
#6  0x00007f1b04bc6ff5 in ngx_http_core_run_phases (r=r@entry=0x7f1b0542a910) at src/http/ngx_http_core_module.c:847
        rc = <optimized out>
        ph = 0x7f1b05484600
        cmcf = <optimized out>
#7  0x00007f1b04bc70e7 in ngx_http_handler (r=r@entry=0x7f1b0542a910) at src/http/ngx_http_core_module.c:830
        cmcf = <optimized out>
#8  0x00007f1b04bd33de in ngx_http_process_request (r=0x7f1b0542a910) at src/http/ngx_http_request.c:1910
        c = 0x7f1b0548ea58
#9  0x00007f1b04bd3da4 in ngx_http_process_request_line (rev=0x7f1b05502490) at src/http/ngx_http_request.c:1022
        n = <optimized out>
        rc = <optimized out>
---Type <return> to continue, or q <return> to quit---
        rv = <optimized out>
        host = {len = 139754024987408, data = 0x400 <error: Cannot access memory at address 0x400>}
        c = 0x7f1b0548ea58
        r = 0x7f1b0542a910
#10 0x00007f1b04bb0f06 in ngx_event_process_posted (cycle=cycle@entry=0x7f1b0503eb10, posted=0x7f1b04ebc6d0 <ngx_posted_events>) at src/event/ngx_event_posted.c:33
        q = 0x7f1b055024e0
        ev = 0x7f1b05502490
#11 0x00007f1b04bb096e in ngx_process_events_and_timers (cycle=cycle@entry=0x7f1b0503eb10) at src/event/ngx_event.c:259
        flags = <optimized out>
        timer = <optimized out>
        delta = <optimized out>
#12 0x00007f1b04bb8775 in ngx_worker_process_cycle (cycle=cycle@entry=0x7f1b0503eb10, data=data@entry=0x1) at src/os/unix/ngx_process_cycle.c:753
        worker = 1
#13 0x00007f1b04bb717a in ngx_spawn_process (cycle=cycle@entry=0x7f1b0503eb10, proc=0x7f1b04bb8720 <ngx_worker_process_cycle>, data=0x1, name=0x7f1b04c5761b "worker process", respawn=respawn@entry=1) at src/os/unix/ngx_process.c:198
        on = 1
        pid = 0
        s = 1
#14 0x00007f1b04bb9e07 in ngx_reap_children (cycle=0x7f1b0503eb10) at src/os/unix/ngx_process_cycle.c:621
        i = <optimized out>
        live = 1
        n = <optimized out>
        ch = {command = 2, pid = 15758, slot = 1, fd = -1}
        ccf = <optimized out>
#15 ngx_master_process_cycle (cycle=0x7f1b0503eb10) at src/os/unix/ngx_process_cycle.c:174
        title = <optimized out>
        p = <optimized out>
        size = <optimized out>
        i = <optimized out>
        n = <optimized out>
        sigio = 0
---Type <return> to continue, or q <return> to quit---
        set = {__val = {0 <repeats 16 times>}}
        itv = {it_interval = {tv_sec = 0, tv_usec = 0}, it_value = {tv_sec = 0, tv_usec = 0}}
        live = <optimized out>
        delay = 0
        ls = <optimized out>
        ccf = 0x7f1b05040850
#16 0x00007f1b04b91545 in main (argc=<optimized out>, argv=<optimized out>) at src/core/nginx.c:367
        b = <optimized out>
        log = <optimized out>
        i = <optimized out>
        cycle = 0x7f1b0503eb10
        init_cycle = {conf_ctx = 0x0, pool = 0x7f1b0503e580, log = 0x7f1b04ea9420 <ngx_log>, new_log = {log_level = 0, file = 0x0, connection = 0, disk_full_time = 0, handler = 0x0, data = 0x0, writer = 0x0, wdata = 0x0, action = 0x0, next = 0x0},
          log_use_stderr = 0, files = 0x0, free_connections = 0x0, free_connection_n = 0, modules = 0x0, modules_n = 0, modules_used = 0, reusable_connections_queue = {prev = 0x0, next = 0x0}, listening = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0},
          paths = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}, config_dump = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}, open_files = {last = 0x0, part = {elts = 0x0, nelts = 0, next = 0x0}, size = 0, nalloc = 0, pool = 0x0},
          shared_memory = {last = 0x0, part = {elts = 0x0, nelts = 0, next = 0x0}, size = 0, nalloc = 0, pool = 0x0}, connection_n = 0, files_n = 0, connections = 0x0, read_events = 0x0, write_events = 0x0, old_cycle = 0x0, conf_file = {len = 21,
            data = 0x7f1b04c52866 "/etc/nginx/nginx.conf"}, conf_param = {len = 29, data = 0x7ffd7b5f8f4b "ss"}, conf_prefix = {len = 11, data = 0x7f1b04c52866 "/etc/nginx/nginx.conf"}, prefix = {len = 17, data = 0x7f1b04c52854 "/usr/share/nginx/"}, lock_file = {
            len = 0, data = 0x0}, hostname = {len = 0, data = 0x0}}
        cd = <optimized out>
        ccf = 0x7f1b05040850
fpeterschmitt commented 8 years ago

Hi,

I can confirm this behavior too, with nginx 1.8.1 and 1.10.1.

I also have multiple ldap servers configured, and I got a segfault only on successful authentication.

hanky commented 8 years ago

I have a similar problem with nginx crashing. I use nginx 1.8.0 on Centos 6.7. The nginx config contains only one ldap server.

My nginx config:

ldap_server fs {
        url ldap://example.com/ou=People,dc=test_app,dc=ru?uid?sub?(objectClass=person);
        require valid_user;
    }

server {
    listen       443;
    server_name  example.com;

    auth_ldap "Forbidden";
    auth_ldap_servers fs;

    ...
}

A core dump:

Core was generated by `nginx: worker process                   '.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004b2e95 in ngx_http_auth_ldap_return_connection ()
Missing separate debuginfos, use: debuginfo-install nginx-1.8.0-13.el6.x86_64
(gdb) bt
#0  0x00000000004b2e95 in ngx_http_auth_ldap_return_connection ()
#1  0x00000000004b3f12 in ngx_http_auth_ldap_handler ()
#2  0x0000000000445305 in ngx_http_core_access_phase ()
#3  0x00000000004421fd in ngx_http_core_run_phases ()
#4  0x00000000004b53a8 in ngx_http_auth_ldap_read_handler ()
#5  0x0000000000437941 in ngx_epoll_process_events ()
#6  0x000000000042d705 in ngx_process_events_and_timers ()
#7  0x00000000004350b5 in ngx_worker_process_cycle ()
#8  0x0000000000433504 in ngx_spawn_process ()
#9  0x000000000043524c in ngx_start_worker_processes ()
#10 0x0000000000435a83 in ngx_master_process_cycle ()
#11 0x0000000000414126 in main ()
ballock commented 7 years ago

I encountered the same behaviour as @quenode and tried investigating. For now I found that this happens when I exceed the configurable amount of LDAP servers.

What looks like is happening is that the module assumes that the array of LDAP server entries has a constant size - this is suggested by the ngx_http_auth_ldap_ldap_server_block function - where ngx_array_create is called with a constant number of members. Just below that, a member is 'pushed' to the array with ngx_array_push, and the exit code of that function is compared to NULL. (See https://github.com/kvspb/nginx-auth-ldap/blob/master/ngx_http_auth_ldap_module.c#L337)

This would all be fine if ngx_array_push would return a NULL when exceeding the size of the array. However, in nginx's sources, in src/core/ngx_array.c you can see that it does its best to extend the array first, and only if it fails, would it return a NULL.

In the meantime, in ngx_http_auth_ldap_init_connections, the servers array is initialized with a new queue in servers[x]->free_connections. When the segfault happens, it's about free_connections which is not initialized for the server member. This gets traced to get executed from ngx_worker_process_init.

This leads to suggest that the array gets extended with a new member after the current members were initialized with free_connections queue.

I am yet looking in the code when the module configuration functions get executed, and when the worker gets init, but the fact this error happens suggests that it's first the worker that gets initialized, then the configuration gets parsed. If this is true, the queue initialization code would need to be moved.

ballock commented 7 years ago

I think I found the reason for this behaviour.

I have a number of nginx .conf files (9 actually), each per site:

ldap_server $name {
    url ldaps://url;
    require valid_user;
}

server {
    server_name name
    ...
    location / {
        auth_ldap_servers $name
        ...
    }
}

The default auth_ldap_servers_size (servers_size) is 7. What this means is that when the configuration parser approaches the first section - ldap_server, it calls the ngx_http_auth_ldap_ldap_server_block function and allocates an array for the servers: cnf->servers = ngx_array_create(cf->pool, cnf->servers_size, sizeof(ngx_http_auth_ldap_server_t));

Then, the parser moves on, and finds the server { stanza, and the location / { stanza underneath, and the auth_ldap_servers directive, for which it fires up the ngx_http_auth_ldap_servers function. This function sets up its own array of servers specifically for the loc_conf (location configuration), and since the location configuration refer to a particular ldap_server $name from the main configuration, it assumes it is safe to use a pointer to the particular mconf->servers->elts that corresponds to the matching $name (note I just copy-pasted some lines from the module to better illustrate the problem, this is not a whole excerpt from it):

s = &((ngx_http_auth_ldap_server_t *) mconf->servers->elts)[j];
server = s;
target = (ngx_http_auth_ldap_server_t **) ngx_array_push(cnf->servers);
*target = server;

Here, mconf is the main configuration structure with the ldap_server stanza and cnf is the location configuration.

When the second (and up to 7th configuration file) is parsed, the ngx_http_auth_ldap_ldap_server_block function adds each new ldap_server to the array with: server = ngx_array_push(cnf->servers); and each location config uses a pointer to refer to the server.

Until now everything is fine. However, when we reach past the 7th element in the main configuration, and the ngx_http_auth_ldap_ldap_server_block function calls server = ngx_array_push(cnf->servers); what nginx does is it extends the array:

    if (a->nelts == a->nalloc) {
            new = ngx_palloc(p, 2 * size);
            ngx_memcpy(new, a->elts, size);
            a->elts = new;
            a->nalloc *= 2;

Now, the main configuration contains an array with 8 servers, each in a new memory location. The upcoming location will have a server pointer to the new array, but all the remaining 7 locations contain a pointer to a memory that is no longer occupied by the main config's servers array.

Since ngx_http_auth_ldap_init_connections is called later, and it is the function that initializes the free_connections and waiting_requests queues in the main configuration, all the 7 location point to memory locations that don't have these queues initialized. And when ngx_http_auth_ldap_return_connection is called, execution proceeds to

    if (!ngx_queue_empty(&c->server->waiting_requests)) {

and it's quite interesting to find that a non-initialized queue is returns from the call as a non-empty one! It's because ngx_queue_empty is actually a macro for (h == (h)->prev) and h->prev is 0x0, while h is a pointer. And that in turn leads to an attempt to remove a 0x0 element from the queue.

ibexmonj commented 6 years ago

Any fix around for this ?