From f21f7086e2eaddc107590de0fdfe1f1731672033 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Wed, 17 Mar 2021 14:23:19 +0100
Subject: [PATCH 1/2] nss client: make innetgr() thread safe

The innetgr() call is expected to be thread safe but SSSD's the current
implementation isn't. In glibc innetgr() is implementend by calling the
setnetgrent(), getnetgrent(), endgrent() sequence with a private context
(struct __netgrent) with provides a member where NSS modules can store
data between the calls.

With this patch setnetgrent() will open a new connection to the NSS
responder and stores the file descriptor in the data member of
__netgrent struct so that the following getnetgrent() and endgrent()
will use the same connection. Since the NSS responder stores the
netgroup lookups related data in a per connection context and a new
thread will open a new connection the implementation is thread safe.

Resolves: https://github.com/SSSD/sssd/issues/5540
---
 src/sss_client/common.c       | 154 +++++++++++++++++++++++-----------
 src/sss_client/nss_netgroup.c |  57 +++++++++++--
 src/sss_client/sss_cli.h      |   6 ++
 3 files changed, 157 insertions(+), 60 deletions(-)

diff --git a/src/sss_client/common.c b/src/sss_client/common.c
index d293329397..17c9beee5f 100644
--- a/src/sss_client/common.c
+++ b/src/sss_client/common.c
@@ -78,6 +78,13 @@ static void sss_cli_close_socket(void)
         sss_cli_sd = -1;
     }
 }
+static void sss_cli_close_socket_fd(int *fd)
+{
+    if ((*fd) != -1) {
+        close((*fd));
+        (*fd) = -1;
+    }
+}
 
 /* Requests:
  *
@@ -87,10 +94,11 @@ static void sss_cli_close_socket(void)
  * byte 12-15: 32bit unsigned (reserved)
  * byte 16-X: (optional) request structure associated to the command code used
  */
-static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
-                                        struct sss_cli_req_data *rd,
-                                        int timeout,
-                                        int *errnop)
+static enum sss_status sss_cli_send_req_fd(enum sss_cli_command cmd,
+                                           struct sss_cli_req_data *rd,
+                                           int *fd,
+                                           int timeout,
+                                           int *errnop)
 {
     uint32_t header[4];
     size_t datasent;
@@ -108,7 +116,7 @@ static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
         int res, error;
 
         *errnop = 0;
-        pfd.fd = sss_cli_sd;
+        pfd.fd = (*fd);
         pfd.events = POLLOUT;
 
         do {
@@ -148,13 +156,13 @@ static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
 
         errno = 0;
         if (datasent < SSS_NSS_HEADER_SIZE) {
-            res = send(sss_cli_sd,
+            res = send((*fd),
                        (char *)header + datasent,
                        SSS_NSS_HEADER_SIZE - datasent,
                        SSS_DEFAULT_WRITE_FLAGS);
         } else {
             rdsent = datasent - SSS_NSS_HEADER_SIZE;
-            res = send(sss_cli_sd,
+            res = send((*fd),
                        (const char *)rd->data + rdsent,
                        rd->len - rdsent,
                        SSS_DEFAULT_WRITE_FLAGS);
@@ -170,7 +178,7 @@ static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
             }
 
             /* Write failed */
-            sss_cli_close_socket();
+            sss_cli_close_socket_fd(fd);
             *errnop = error;
             return SSS_STATUS_UNAVAIL;
         }
@@ -190,10 +198,11 @@ static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
  * byte 16-X: (optional) reply structure associated to the command code used
  */
 
-static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
-                                        int timeout,
-                                        uint8_t **_buf, int *_len,
-                                        int *errnop)
+static enum sss_status sss_cli_recv_rep_fd(enum sss_cli_command cmd,
+                                           int *fd,
+                                           int timeout,
+                                           uint8_t **_buf, int *_len,
+                                           int *errnop)
 {
     uint32_t header[4];
     size_t datarecv;
@@ -217,7 +226,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
         int bufrecv;
         int res, error;
 
-        pfd.fd = sss_cli_sd;
+        pfd.fd = (*fd);
         pfd.events = POLLIN;
 
         do {
@@ -254,19 +263,19 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
             break;
         }
         if (*errnop) {
-            sss_cli_close_socket();
+            sss_cli_close_socket_fd(fd);
             ret = SSS_STATUS_UNAVAIL;
             goto failed;
         }
 
         errno = 0;
         if (datarecv < SSS_NSS_HEADER_SIZE) {
-            res = read(sss_cli_sd,
+            res = read((*fd),
                        (char *)header + datarecv,
                        SSS_NSS_HEADER_SIZE - datarecv);
         } else {
             bufrecv = datarecv - SSS_NSS_HEADER_SIZE;
-            res = read(sss_cli_sd,
+            res = read((*fd),
                        (char *) buf + bufrecv,
                        header[0] - datarecv);
         }
@@ -285,7 +294,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
              * since the transaction has failed half way
              * through. */
 
-            sss_cli_close_socket();
+            sss_cli_close_socket_fd(fd);
             *errnop = error;
             ret = SSS_STATUS_UNAVAIL;
             goto failed;
@@ -299,7 +308,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
              * been read, do checks and proceed */
             if (header[2] != 0) {
                 /* server side error */
-                sss_cli_close_socket();
+                sss_cli_close_socket_fd(fd);
                 *errnop = header[2];
                 if (*errnop == EAGAIN) {
                     ret = SSS_STATUS_TRYAGAIN;
@@ -311,7 +320,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
             }
             if (header[1] != cmd) {
                 /* wrong command id */
-                sss_cli_close_socket();
+                sss_cli_close_socket_fd(fd);
                 *errnop = EBADMSG;
                 ret = SSS_STATUS_UNAVAIL;
                 goto failed;
@@ -320,7 +329,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
                 len = header[0] - SSS_NSS_HEADER_SIZE;
                 buf = malloc(len);
                 if (!buf) {
-                    sss_cli_close_socket();
+                    sss_cli_close_socket_fd(fd);
                     *errnop = ENOMEM;
                     ret = SSS_STATUS_UNAVAIL;
                     goto failed;
@@ -330,7 +339,7 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
     }
 
     if (pollhup) {
-        sss_cli_close_socket();
+        sss_cli_close_socket_fd(fd);
     }
 
     *_len = len;
@@ -345,9 +354,10 @@ static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
 
 /* this function will check command codes match and returned length is ok */
 /* repbuf and replen report only the data section not the header */
-static enum sss_status sss_cli_make_request_nochecks(
+static enum sss_status sss_cli_make_request_nochecks_fd(
                                        enum sss_cli_command cmd,
                                        struct sss_cli_req_data *rd,
+                                       int *fd,
                                        int timeout,
                                        uint8_t **repbuf, size_t *replen,
                                        int *errnop)
@@ -357,13 +367,13 @@ static enum sss_status sss_cli_make_request_nochecks(
     int len = 0;
 
     /* send data */
-    ret = sss_cli_send_req(cmd, rd, timeout, errnop);
+    ret = sss_cli_send_req_fd(cmd, rd, fd, timeout, errnop);
     if (ret != SSS_STATUS_SUCCESS) {
         return ret;
     }
 
     /* data sent, now get reply */
-    ret = sss_cli_recv_rep(cmd, timeout, &buf, &len, errnop);
+    ret = sss_cli_recv_rep_fd(cmd, fd, timeout, &buf, &len, errnop);
     if (ret != SSS_STATUS_SUCCESS) {
         return ret;
     }
@@ -385,11 +395,23 @@ static enum sss_status sss_cli_make_request_nochecks(
     return SSS_STATUS_SUCCESS;
 }
 
+static enum sss_status sss_cli_make_request_nochecks(
+                                       enum sss_cli_command cmd,
+                                       struct sss_cli_req_data *rd,
+                                       int timeout,
+                                       uint8_t **repbuf, size_t *replen,
+                                       int *errnop)
+{
+    return sss_cli_make_request_nochecks_fd(cmd, rd, &sss_cli_sd, timeout,
+                                            repbuf, replen, errnop);
+}
+
 /* GET_VERSION Reply:
  * 0-3: 32bit unsigned version number
  */
 
-static bool sss_cli_check_version(const char *socket_name, int timeout)
+static bool sss_cli_check_version_fd(int *fd, const char *socket_name,
+                                     int timeout)
 {
     uint8_t *repbuf = NULL;
     size_t replen;
@@ -419,8 +441,8 @@ static bool sss_cli_check_version(const char *socket_name, int timeout)
     req.len = sizeof(expected_version);
     req.data = &expected_version;
 
-    nret = sss_cli_make_request_nochecks(SSS_GET_VERSION, &req, timeout,
-                                         &repbuf, &replen, &errnop);
+    nret = sss_cli_make_request_nochecks_fd(SSS_GET_VERSION, &req, fd, timeout,
+                                            &repbuf, &replen, &errnop);
     if (nret != SSS_STATUS_SUCCESS) {
         return false;
     }
@@ -634,9 +656,9 @@ static int sss_cli_open_socket(int *errnop, const char *socket_name, int timeout
     return sd;
 }
 
-static enum sss_status sss_cli_check_socket(int *errnop,
-                                            const char *socket_name,
-                                            int timeout)
+static enum sss_status sss_cli_check_socket_fd(int *fd, int *errnop,
+                                               const char *socket_name,
+                                               int timeout)
 {
     static pid_t mypid;
     struct stat mysb;
@@ -644,7 +666,7 @@ static enum sss_status sss_cli_check_socket(int *errnop,
     int ret;
 
     if (getpid() != mypid) {
-        ret = fstat(sss_cli_sd, &mysb);
+        ret = fstat((*fd), &mysb);
         if (ret == 0) {
             if (S_ISSOCK(mysb.st_mode) &&
                 mysb.st_dev == sss_cli_sb.st_dev &&
@@ -652,17 +674,18 @@ static enum sss_status sss_cli_check_socket(int *errnop,
                 sss_cli_close_socket();
             }
         }
-        sss_cli_sd = -1;
+        (*fd) = -1;
         mypid = getpid();
     }
 
+
     /* check if the socket has been closed on the other side */
-    if (sss_cli_sd != -1) {
+    if ((*fd) != -1) {
         struct pollfd pfd;
         int res, error;
 
         *errnop = 0;
-        pfd.fd = sss_cli_sd;
+        pfd.fd = (*fd);
         pfd.events = POLLIN | POLLOUT;
 
         do {
@@ -699,7 +722,7 @@ static enum sss_status sss_cli_check_socket(int *errnop,
             return SSS_STATUS_SUCCESS;
         }
 
-        sss_cli_close_socket();
+        sss_cli_close_socket_fd(fd);
     }
 
     mysd = sss_cli_open_socket(errnop, socket_name, timeout);
@@ -707,24 +730,33 @@ static enum sss_status sss_cli_check_socket(int *errnop,
         return SSS_STATUS_UNAVAIL;
     }
 
-    sss_cli_sd = mysd;
+    (*fd) = mysd;
 
-    if (sss_cli_check_version(socket_name, timeout)) {
+    if (sss_cli_check_version_fd(fd, socket_name, timeout)) {
         return SSS_STATUS_SUCCESS;
     }
 
-    sss_cli_close_socket();
+    sss_cli_close_socket_fd(fd);
     *errnop = EFAULT;
     return SSS_STATUS_UNAVAIL;
 }
 
+static enum sss_status sss_cli_check_socket(int *errnop,
+                                            const char *socket_name,
+                                            int timeout)
+{
+    return sss_cli_check_socket_fd(&sss_cli_sd, errnop, socket_name, timeout);
+}
+
 /* this function will check command codes match and returned length is ok */
 /* repbuf and replen report only the data section not the header */
-enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
-                                             struct sss_cli_req_data *rd,
-                                             int timeout,
-                                             uint8_t **repbuf, size_t *replen,
-                                             int *errnop)
+enum nss_status sss_nss_make_request_timeout_fd(enum sss_cli_command cmd,
+                                                struct sss_cli_req_data *rd,
+                                                int *fd,
+                                                int timeout,
+                                                uint8_t **repbuf,
+                                                size_t *replen,
+                                                int *errnop)
 {
     enum sss_status ret;
     char *envval;
@@ -735,7 +767,7 @@ enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
         return NSS_STATUS_NOTFOUND;
     }
 
-    ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME, timeout);
+    ret = sss_cli_check_socket_fd(fd, errnop, SSS_NSS_SOCKET_NAME, timeout);
     if (ret != SSS_STATUS_SUCCESS) {
 #ifdef NONSTANDARD_SSS_NSS_BEHAVIOUR
         *errnop = 0;
@@ -746,11 +778,11 @@ enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
 #endif
     }
 
-    ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
-                                        errnop);
+    ret = sss_cli_make_request_nochecks_fd(cmd, rd, fd, timeout, repbuf, replen,
+                                           errnop);
     if (ret == SSS_STATUS_UNAVAIL && *errnop == EPIPE) {
         /* try reopen socket */
-        ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME, timeout);
+        ret = sss_cli_check_socket_fd(fd, errnop, SSS_NSS_SOCKET_NAME, timeout);
         if (ret != SSS_STATUS_SUCCESS) {
 #ifdef NONSTANDARD_SSS_NSS_BEHAVIOUR
             *errnop = 0;
@@ -762,8 +794,8 @@ enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
         }
 
         /* and make request one more time */
-        ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
-                                            errnop);
+        ret = sss_cli_make_request_nochecks_fd(cmd, rd, fd, timeout,
+                                               repbuf, replen, errnop);
     }
     switch (ret) {
     case SSS_STATUS_TRYAGAIN:
@@ -782,13 +814,33 @@ enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
     }
 }
 
+enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
+                                             struct sss_cli_req_data *rd,
+                                             int timeout,
+                                             uint8_t **repbuf, size_t *replen,
+                                             int *errnop)
+{
+    return sss_nss_make_request_timeout_fd(cmd, rd, &sss_cli_sd, timeout,
+                                           repbuf, replen, errnop);
+}
+
+enum nss_status sss_nss_make_request_fd(enum sss_cli_command cmd,
+                                        struct sss_cli_req_data *rd,
+                                        int *fd,
+                                        uint8_t **repbuf, size_t *replen,
+                                        int *errnop)
+{
+    return sss_nss_make_request_timeout_fd(cmd, rd, fd, SSS_CLI_SOCKET_TIMEOUT,
+                                           repbuf, replen, errnop);
+}
+
 enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
                                      struct sss_cli_req_data *rd,
                                      uint8_t **repbuf, size_t *replen,
                                      int *errnop)
 {
-    return sss_nss_make_request_timeout(cmd, rd, SSS_CLI_SOCKET_TIMEOUT,
-                                        repbuf, replen, errnop);
+    return sss_nss_make_request_fd(cmd, rd, &sss_cli_sd, repbuf, replen,
+                                   errnop);
 }
 
 int sss_pac_check_and_open(void)
diff --git a/src/sss_client/nss_netgroup.c b/src/sss_client/nss_netgroup.c
index 2fc88f8ae4..34faaf3859 100644
--- a/src/sss_client/nss_netgroup.c
+++ b/src/sss_client/nss_netgroup.c
@@ -44,6 +44,7 @@
  * Replies:
  *
  * 0-3: 32bit unsigned number of results N
+ *      will be replaced with the file descriptor between calls
  * 4-7: 32bit unsigned (reserved/padding)
  *  For each result:
  *  8-11: 32bit unsigned type of result
@@ -167,6 +168,8 @@ enum nss_status _nss_sss_setnetgrent(const char *netgroup,
     char *name;
     size_t name_len;
     errno_t ret;
+    int my_fd = -1;
+    int32_t fd32 = -1;
 
     if (!netgroup) return NSS_STATUS_NOTFOUND;
 
@@ -191,8 +194,8 @@ enum nss_status _nss_sss_setnetgrent(const char *netgroup,
     rd.data = name;
     rd.len = name_len + 1;
 
-    nret = sss_nss_make_request(SSS_NSS_SETNETGRENT, &rd,
-                                &repbuf, &replen, &errnop);
+    nret = sss_nss_make_request_fd(SSS_NSS_SETNETGRENT, &rd, &my_fd,
+                                   &repbuf, &replen, &errnop);
     free(name);
     if (nret != NSS_STATUS_SUCCESS) {
         errno = errnop;
@@ -210,6 +213,20 @@ enum nss_status _nss_sss_setnetgrent(const char *netgroup,
     }
 
     free(repbuf);
+    if (my_fd < INT32_MAX) {
+        fd32 = my_fd;
+    } else {
+        nret = NSS_STATUS_TRYAGAIN;
+        goto out;
+    }
+    result->data_size = 0;
+    result->data = malloc(sizeof(int32_t));
+    if (result->data == NULL) {
+        nret = NSS_STATUS_TRYAGAIN;
+        goto out;
+    }
+    SAFEALIGN_COPY_INT32(result->data, &fd32, NULL);
+
     nret = NSS_STATUS_SUCCESS;
 
 out:
@@ -229,17 +246,24 @@ static enum nss_status internal_getnetgrent_r(struct __netgrent *result,
     enum nss_status nret;
     uint32_t num_entries;
     int ret;
+    int32_t fd32;
+    int my_fd = -1;
 
     /* Caught once glibc passing in buffer == 0x0 */
     if (!buffer || !buflen) {
-	*errnop = ERANGE;
-	return NSS_STATUS_TRYAGAIN;
+        *errnop = ERANGE;
+        return NSS_STATUS_TRYAGAIN;
+    }
+
+    if (result->data != NULL) {
+        SAFEALIGN_COPY_INT32(&fd32, result->data, NULL);
+        my_fd = fd32;
     }
 
     /* If we're already processing result data, continue to
      * return it.
      */
-    if (result->data != NULL &&
+    if (result->data != NULL && result->data_size != 0 &&
         result->idx.position < result->data_size) {
 
         repbuf = (uint8_t *) result->data + result->idx.position;
@@ -268,8 +292,8 @@ static enum nss_status internal_getnetgrent_r(struct __netgrent *result,
     rd.len = sizeof(uint32_t);
     rd.data = &num_entries;
 
-    nret = sss_nss_make_request(SSS_NSS_GETNETGRENT, &rd,
-                                &repbuf, &replen, errnop);
+    nret = sss_nss_make_request_fd(SSS_NSS_GETNETGRENT, &rd, &my_fd,
+                                   &repbuf, &replen, errnop);
     if (nret != NSS_STATUS_SUCCESS) {
         return nret;
     }
@@ -283,6 +307,13 @@ static enum nss_status internal_getnetgrent_r(struct __netgrent *result,
         return NSS_STATUS_RETURN;
     }
 
+    if (my_fd < INT32_MAX) {
+        fd32 = my_fd;
+    } else {
+        free(repbuf);
+        return NSS_STATUS_TRYAGAIN;
+    }
+    SAFEALIGN_COPY_INT32(repbuf, &fd32, NULL);
     result->data = (char *) repbuf;
     result->data_size = replen;
     /* skip metadata fields */
@@ -310,20 +341,28 @@ enum nss_status _nss_sss_endnetgrent(struct __netgrent *result)
     enum nss_status nret;
     int errnop;
     int saved_errno = errno;
+    int my_fd = -1;
+    int fd32;
 
     sss_nss_lock();
 
+    if (result->data != NULL) {
+        SAFEALIGN_COPY_INT32(&fd32, result->data, NULL);
+        my_fd = fd32;
+    }
+
     /* make sure we do not have leftovers, and release memory */
     CLEAR_NETGRENT_DATA(result);
 
-    nret = sss_nss_make_request(SSS_NSS_ENDNETGRENT,
-                                NULL, NULL, NULL, &errnop);
+    nret = sss_nss_make_request_fd(SSS_NSS_ENDNETGRENT,
+                                   NULL, &my_fd, NULL, NULL, &errnop);
     if (nret != NSS_STATUS_SUCCESS) {
         errno = errnop;
     } else {
         errno = saved_errno;
     }
 
+    close(my_fd);
     sss_nss_unlock();
     return nret;
 }
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 2c3c71bc41..cce239a187 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -636,6 +636,12 @@ enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
                                      uint8_t **repbuf, size_t *replen,
                                      int *errnop);
 
+enum nss_status sss_nss_make_request_fd(enum sss_cli_command cmd,
+                                        struct sss_cli_req_data *rd,
+                                        int *fd,
+                                        uint8_t **repbuf, size_t *replen,
+                                        int *errnop);
+
 enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
                                              struct sss_cli_req_data *rd,
                                              int timeout,

From fd22979538c0664cd936aff96f6541ad8d1290c2 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 16 Mar 2021 18:27:57 +0100
Subject: [PATCH 2/2] intg test: test is innetgr() is thread-safe

This integration test adds 2 large netgroups in LDAP and runs a program
with 2 threads looking up those netgroups in parallel.

Resolves: https://github.com/SSSD/sssd/issues/5540
---
 src/tests/intg/Makefile.am                | 14 +++++-
 src/tests/intg/sss_netgroup_thread_test.c | 60 +++++++++++++++++++++++
 src/tests/intg/test_netgroup.py           | 24 +++++++++
 3 files changed, 97 insertions(+), 1 deletion(-)
 create mode 100644 src/tests/intg/sss_netgroup_thread_test.c

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index f717214ad3..133f07423a 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -62,6 +62,18 @@ getsockopt_wrapper_la_LDFLAGS = \
     -avoid-version \
     -module
 
+bin_PROGRAMS = sss_netgroup_thread_test
+
+sss_netgroup_thread_test_SOURCES = \
+    sss_netgroup_thread_test.c \
+    $(NULL)
+sss_netgroup_thread_test_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+sss_netgroup_thread_test_LDADD = \
+    -lpthread \
+    $(NULL)
+
 dist_dbussysconf_DATA = cwrap-dbus-system.conf
 
 install-data-hook:
@@ -162,7 +174,7 @@ clean-local:
 PAM_CERT_DB_PATH="$(abs_builddir)/../test_CA/SSSD_test_CA.pem"
 SOFTHSM2_CONF="$(abs_builddir)/../test_CA/softhsm2_one.conf"
 
-intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains
+intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains sss_netgroup_thread_test
 	pipepath="$(DESTDIR)$(pipepath)"; \
 	if test $${#pipepath} -gt 80; then \
 	    echo "error: Pipe directory path too long," \
diff --git a/src/tests/intg/sss_netgroup_thread_test.c b/src/tests/intg/sss_netgroup_thread_test.c
new file mode 100644
index 0000000000..682f2547ad
--- /dev/null
+++ b/src/tests/intg/sss_netgroup_thread_test.c
@@ -0,0 +1,60 @@
+#define _GNU_SOURCE /* for pthread_yield */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+
+
+struct data {
+    char *group;
+    char *host;
+    char *user;
+    char *domain;
+    bool *failed;
+};
+
+static void *full_netgroup(void *arg)
+{
+    int ret;
+    size_t c = 0;
+    struct data *data = arg;
+
+    do {
+        ret = innetgr(data->group, data->host, data->user, data->domain);
+        if (ret != 1) {
+            *(data->failed) = true;
+        }
+        c++;
+    } while (!*(data->failed) && c<100000);
+
+    pthread_exit(NULL);
+}
+
+int main()
+{
+    pthread_t thread[2];
+    bool failed = false;
+
+    struct data data[3] = {{"ng1", "host1", "user924", "domain1", &failed},
+                           {"ng2", "host2", "user925", "domain2", &failed},
+                           {NULL, NULL, NULL, NULL, NULL}};
+
+
+    pthread_create(&thread[0], NULL, full_netgroup, &data[0]);
+    pthread_create(&thread[1], NULL, full_netgroup, &data[1]);
+
+    pthread_join(thread[1], NULL);
+    pthread_join(thread[0], NULL);
+
+    if (failed) {
+        printf ("Test failed.\n");
+    }
+
+    return failed ? 1 : 0;
+}
diff --git a/src/tests/intg/test_netgroup.py b/src/tests/intg/test_netgroup.py
index 3d7f980e9b..511daa4d18 100644
--- a/src/tests/intg/test_netgroup.py
+++ b/src/tests/intg/test_netgroup.py
@@ -510,3 +510,27 @@ def test_offline_netgroups(add_tripled_netgroup):
     res, _, netgrps = get_sssd_netgroups("tripled_netgroup")
     assert res == NssReturnCode.SUCCESS
     assert netgrps == [("host", "user", "domain")]
+
+@pytest.fixture
+def add_thread_test_netgroup(request, ldap_conn):
+    ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+    triple_list = []
+    for i in range(1,999):
+        triple_list.append("(host1,user" + str(i) + ",domain1)");
+    ent_list.add_netgroup("ng1", triple_list)
+
+    triple_list = []
+    for i in range(1,999):
+        triple_list.append("(host2,user" + str(i) + ",domain2)");
+    ent_list.add_netgroup("ng2", triple_list)
+
+    create_ldap_fixture(request, ldap_conn, ent_list)
+    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+    return None
+
+def test_innetgr_with_threads(add_thread_test_netgroup):
+
+    subprocess.check_call(["sss_netgroup_thread_test"])
