qiling icon indicating copy to clipboard operation
qiling copied to clipboard

Meet gevent exception

Open YuYutian opened this issue 4 years ago • 5 comments

*Describe the bug when simulating proftpd-1.3.0, i meet a bug.

Sample Code

ql = Qiling(["proftpd", "-n"], multithread=True)
ql.run()

ERROR Traceback (most recent call last): File "1.py", line 13, in my_sandbox(["proftpd2","-n"], "/home/yuyutian/Desktop/DOP/i386-linux-gnu") File "1.py", line 10, in my_sandbox ql.run() File "/home/yuyutian/.local/lib/python3.6/site-packages/qiling/core.py", line 707, in run self.os.run() File "/home/yuyutian/.local/lib/python3.6/site-packages/qiling/os/linux/linux.py", line 123, in run thread_management.run() File "/home/yuyutian/.local/lib/python3.6/site-packages/qiling/os/linux/thread.py", line 603, in run gevent.joinall([self.main_thread], raise_error=True) File "src/gevent/greenlet.py", line 1057, in gevent._gevent_cgreenlet.joinall File "src/gevent/greenlet.py", line 1070, in gevent._gevent_cgreenlet.joinall File "src/gevent/_hub_primitives.py", line 185, in gevent._gevent_c_hub_primitives._WaitIterator.next File "src/gevent/_hub_primitives.py", line 176, in gevent._gevent_c_hub_primitives._WaitIterator.next File "src/gevent/_waiter.py", line 195, in gevent._gevent_c_waiter.MultipleWaiter.get File "src/gevent/_waiter.py", line 154, in gevent._gevent_c_waiter.Waiter.get File "src/gevent/_greenlet_primitives.py", line 61, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch File "src/gevent/_greenlet_primitives.py", line 61, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch File "src/gevent/_greenlet_primitives.py", line 65, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch File "src/gevent/_gevent_c_greenlet_primitives.pxd", line 35, in gevent._gevent_c_greenlet_primitives._greenlet_switch gevent.exceptions.LoopExit: This operation would block forever Hub: <Hub '' at 0x7fededcece10 epoll default pending=0 ref=0 fileno=3 thread_ident=0x7fee35e20740> Handles: []

Additional context i notice that you have comment just next to the bug:

If we get exceptions from gevent here, it means a critical bug related to multithread.

Please fire an issue if you encounter an exception from gevent.

SO HOW TO FIX IT? THX!

YuYutian avatar Nov 08 '21 16:11 YuYutian

Log && Reproduction binary.

wtdcode avatar Nov 08 '21 19:11 wtdcode

Log && Reproduction binary.

hi, i add some log operations and rebuild the binary. then i rerun it in 'no-daemon' mode and 'daemon' mode.

when i run proftpd in 'daemon' mode, it will daemonize itself: clone(with syscall fork) a new process(in simulator it is thread 8143) and terminate the old 'process' / thread(2000). after the old 'process' exit, the new 'process' runs into a daemon loop to select IO. this is the position where new process throws the exception.

image image

in terms of 'no-daemon' mode, the situation is similar. proftpd will start loop in thread 2000, and block at syscall select. at this time the log is like:

image

when i try 'ftp 127.0.0.1 8021' to connect this server process, it breaks down. image image image

client terminal is like: image

thank you very much for your answer, i will provide more information if necessary. keep trying....

static void daemon_loop(void) {
  fd_set listenfds;
  conn_t *listen_conn;
  int fd, maxfd;
  int i, err_count = 0;
  unsigned long nconnects = 0UL;
  time_t last_error;
  struct timeval tv;
  static int running = 0;

  set_proc_title("(accepting connections)");

  time(&last_error);

  while (TRUE) {
    pr_log_pri(PR_LOG_ERR, "[yyt] daemon loop");
    run_schedule();

    FD_ZERO(&listenfds);
    maxfd = 0;
    maxfd = pr_ipbind_listen(&listenfds);

    /* Monitor children pipes */
    maxfd = semaphore_fds(&listenfds, maxfd);

    /* Check for ftp shutdown message file */
    switch (check_shutmsg(&shut, &deny, &disc, shutmsg, sizeof(shutmsg))) {
      case 1:
        if (!shutdownp)
          disc_children();
        shutdownp = 1;
        break;

      case 0:
        shutdownp = 0;
        deny = disc = (time_t) 0;
        break;
    }
   
    pr_log_pri(PR_LOG_ERR, "[yyt] shutdownp");
    if (shutdownp) {
      tv.tv_sec = 5L;
      tv.tv_usec = 0L;

    } else {

      tv.tv_sec = PR_TUNABLE_SELECT_TIMEOUT;
      tv.tv_usec = 0L;
    }

    /* If running (a flag signaling whether proftpd is just starting up)
     * AND shutdownp (a flag signalling the present of /etc/shutmsg) are
     * true, then log an error stating this -- but don't stop the server.
     */
    pr_log_pri(PR_LOG_ERR, "[yyt] shutdownp && !running");
    if (shutdownp && !running) {

      /* Check the value of the deny time_t struct w/ the current time.
       * If the deny time has passed, log that all incoming connections
       * will be refused.  If not, note the date at which they will be
       * refused in the future.
       */
      time_t now = time(NULL);

      if (difftime(deny, now) < 0.0) {
        pr_log_pri(PR_LOG_ERR, PR_SHUTMSG_PATH
          " present: all incoming connections will be refused.");

      } else {
        pr_log_pri(PR_LOG_ERR, PR_SHUTMSG_PATH " present: incoming connections "
          "will be denied starting %s", CHOP(ctime(&deny)));
      }
    }

    running = 1;

    pr_log_pri(PR_LOG_ERR, "[yyt] before syscall: select");
    i = select(maxfd + 1, &listenfds, NULL, NULL, &tv);
    pr_log_pri(PR_LOG_ERR, "[yyt] after syscall: select");

    if (i == -1 && errno == EINTR) {
      pr_signals_handle();
      continue;
    }

    if (have_dead_child) {
      sigset_t sig_set;

      sigemptyset(&sig_set);
      sigaddset(&sig_set, SIGCHLD);
      sigaddset(&sig_set, SIGTERM);
      pr_alarms_block();
      sigprocmask(SIG_BLOCK, &sig_set, NULL);

      have_dead_child = FALSE;
      child_update();

      sigprocmask(SIG_UNBLOCK, &sig_set, NULL);
      pr_alarms_unblock();
    }

    if (i == -1) {
      time_t this_error;

      time(&this_error);

      if ((this_error - last_error) <= 5 && err_count++ > 10) {
        pr_log_pri(PR_LOG_ERR, "Fatal: select() failing repeatedly, shutting "
          "down.");
        exit(1);

      } else if ((this_error - last_error) > 5) {
        last_error = this_error;
        err_count = 0;
      }

      pr_log_pri(PR_LOG_NOTICE, "select() failed in daemon_loop(): %s",
        strerror(errno));
    }

    if (i == 0)
      continue;

    /* Reset the connection counter.  Take into account this current
     * connection, which does not (yet) have an entry in the child list.
     */
    nconnects = 1UL;

    /* See if child semaphore pipes have signaled */
    if (child_count()) {
      pr_child_t *ch;
      time_t now = time(NULL);

      for (ch = child_get(NULL); ch; ch = child_get(ch)) {
    if (ch->ch_pipefd != -1 &&
            FD_ISSET(ch->ch_pipefd, &listenfds)) {
      close(ch->ch_pipefd);
      ch->ch_pipefd = -1;
    }

        /* While we're looking, tally up the number of children forked in
         * the past interval.
         */
        if (ch->ch_when >= (now - (unsigned long) max_connect_interval))
          nconnects++;
      }
    }

    pr_signals_handle();

    /* Accept the connection. */
    listen_conn = pr_ipbind_accept_conn(&listenfds, &fd);

    /* Fork off servers to handle each connection our job is to get back to
     * answering connections asap, so leave the work of determining which
     * server the connection is for to our child.
     */

    if (listen_conn) {

      /* Check for exceeded MaxInstances. */
      if (ServerMaxInstances && (child_count() >= ServerMaxInstances)) {
        pr_event_generate("core.max-instances", NULL);
       
        pr_log_pri(PR_LOG_WARNING,
          "MaxInstances (%d) reached, new connection denied",
          ServerMaxInstances);
        close(fd);

      /* Check for exceeded MaxConnectionRate. */
      } else if (max_connects && (nconnects > max_connects)) {
        pr_event_generate("core.max-connection-rate", NULL);

        pr_log_pri(PR_LOG_WARNING,
          "MaxConnectionRate (%lu/%u secs) reached, new connection denied",
          max_connects, max_connect_interval);
        close(fd);

      /* Fork off a child to handle the connection. */
      } else {
        pr_log_pri(PR_LOG_ERR, "[yyt] before fork_server(fd, listen_conn, FALSE);");
        fork_server(fd, listen_conn, FALSE);
        pr_log_pri(PR_LOG_ERR, "[yyt] parent: after fork_server(fd, listen_conn, FALSE);");
      }
    }
#ifdef PR_DEVEL_NO_DAEMON
    /* Do not continue the while() loop here if not daemonizing. */
    break;
#endif /* PR_DEVEL_NO_DAEMON */
  }
}

YuYutian avatar Nov 09 '21 07:11 YuYutian

I wasn't able to find the executable online (only a recent, non vulnerable, version of it). Would you mind sharing your rootfs directory with all the relevant binaries and libs inside to let us reproduce?

elicn avatar Nov 10 '21 14:11 elicn

I wasn't able to find the executable online (only a recent, non vulnerable, version of it). Would you mind sharing your rootfs directory with all the relevant binaries and libs inside to let us reproduce?

Sorry for the late reply! Sure! i get the binaries from this url: https://drive.google.com/file/d/0B_6p5h2gdgmoTV9ON0xYZWpMRTg/view, you can run /usr/local/proftpd-1.3.0/sbin/proftpd. but i think maybe its better to provide you all my rootfs directory. if you want so, could you give me an email address please? i ll pack it and send to you ;-) keep contact

YuYutian avatar Nov 14 '21 08:11 YuYutian

The access to link is denied. Please grant the necessary access, and share your rootfs directory content (preferably as a tar.gz file).

elicn avatar Nov 16 '21 10:11 elicn

Close for now.

We updated the codebase for Qiling and Unicorn since this issue being posted.

Feel free to try the latest version.

xwings avatar Oct 06 '22 03:10 xwings