Victor wrote:
> It looks like the ps_early_dnsbl_event() callback is called here with
> a client context that has been deallocated (client disconnected, ...
> before all the DNS blacklist lookups completed). The callback should
> be cancelled when the client state is deleted.
> 
> The "dnsbl_score_cache" table contains per-client address call-back
> lists for each connection that wants the score results, and this callback
> list does not appear to be pruned when a client disconnects...

The following patch for postfix-2.8-20100923 eliminates two race
conditions. Both are triggered when a client makes N > 1 simultaneous
connections, and then disconnects M < N connections before postscreen
has delivered the DNSBL score to the "pseudo" threads for those M
connections.

This problem was introduced with postfix-2.8-20100914.

The patch looks large, but that is because it adds one additional
argument to a function that is called from several places.

I have not tried to apply the patch to postfix versions between
postfix-2.8-20100914 and postfix-2.8-20100923. Instead, I'll
upload an updated snapshot later today.

        Wietse

diff -cr /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen.h 
src/postscreen/postscreen.h
*** /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen.h   Thu Sep 23 
11:22:30 2010
--- src/postscreen/postscreen.h Wed Oct  6 15:06:13 2010
***************
*** 49,54 ****
--- 49,55 ----
      time_t  pregr_stamp;              /* pregreet expiration time */
      time_t  dnsbl_stamp;              /* dnsbl expiration time */
      VSTRING *dnsbl_reply;             /* dnsbl reject text */
+     int     dnsbl_index;              /* dnsbl request index */
      /* Built-in SMTP protocol engine. */
      time_t  pipel_stamp;              /* pipelining expiration time */
      time_t  nsmtp_stamp;              /* non-smtp command expiration time */
***************
*** 378,385 ****
    * postscreen_dnsbl.c
    */
  extern void ps_dnsbl_init(void);
! extern int ps_dnsbl_retrieve(const char *, const char **);
! extern void ps_dnsbl_request(const char *, void (*) (int, char *), char *);
  
   /*
    * postscreen_tests.c
--- 379,386 ----
    * postscreen_dnsbl.c
    */
  extern void ps_dnsbl_init(void);
! extern int ps_dnsbl_retrieve(const char *, const char **, int);
! extern int ps_dnsbl_request(const char *, void (*) (int, char *), char *);
  
   /*
    * postscreen_tests.c
diff -cr /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen_dnsbl.c 
src/postscreen/postscreen_dnsbl.c
*** /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen_dnsbl.c     Thu Sep 
16 08:01:40 2010
--- src/postscreen/postscreen_dnsbl.c   Wed Oct  6 16:11:37 2010
***************
*** 8,21 ****
  /*
  /*    void    ps_dnsbl_init(void)
  /*
! /*    void    ps_dnsbl_request(client_addr, callback, context)
  /*    char    *client_addr;
  /*    void    (*callback)(int, char *);
  /*    char    *context;
  /*
! /*    int     ps_dnsbl_retrieve(client_addr, dnsbl_name)
  /*    char    *client_addr;
  /*    const char **dnsbl_name;
  /* DESCRIPTION
  /*    This module implements preliminary support for DNSBL lookups.
  /*    Multiple requests for the same information are handled with
--- 8,22 ----
  /*
  /*    void    ps_dnsbl_init(void)
  /*
! /*    int     ps_dnsbl_request(client_addr, callback, context)
  /*    char    *client_addr;
  /*    void    (*callback)(int, char *);
  /*    char    *context;
  /*
! /*    int     ps_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index)
  /*    char    *client_addr;
  /*    const char **dnsbl_name;
+ /*    int     dnsbl_index;
  /* DESCRIPTION
  /*    This module implements preliminary support for DNSBL lookups.
  /*    Multiple requests for the same information are handled with
***************
*** 33,38 ****
--- 34,41 ----
  /*    on to the callback function. The callback should ignore its
  /*    first argument (it exists for compatibility with Postfix
  /*    generic event infrastructure).
+ /*    The result value is the index for the ps_dnsbl_retrieve()
+ /*    call.
  /*
  /*    ps_dnsbl_retrieve() retrieves the result score requested with
  /*    ps_dnsbl_request() and decrements the reference count. It
***************
*** 141,146 ****
--- 144,162 ----
        (sp)->index = 0; \
      } while (0)
  
+ #define PS_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1)
+ 
+ #define PS_CALL_BACK_CANCEL(sp, idx) do { \
+       PS_CALL_BACK_ENTRY *_cb_; \
+       if ((idx) < 0 || (idx) >= (sp)->index) \
+           msg_panic("%s: index %d must be >= 0 and < %d", \
+                     myname, (idx), (sp)->index); \
+       _cb_ = (sp)->table + (idx); \
+       event_cancel_timer(_cb_->callback, _cb_->context); \
+       _cb_->callback = 0; \
+       _cb_->context = 0; \
+     } while (0)
+ 
  #define PS_CALL_BACK_EXTEND(hp, sp) do { \
        if ((sp)->index >= (sp)->limit) { \
            int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5); \
***************
*** 160,166 ****
  #define PS_CALL_BACK_NOTIFY(sp, ev) do { \
        PS_CALL_BACK_ENTRY *_cb_; \
        for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
!           _cb_->callback((ev), _cb_->context); \
      } while (0)
  
  #define PS_NULL_EVENT (0)
--- 176,183 ----
  #define PS_CALL_BACK_NOTIFY(sp, ev) do { \
        PS_CALL_BACK_ENTRY *_cb_; \
        for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
!           if (_cb_->callback != 0) \
!               _cb_->callback((ev), _cb_->context); \
      } while (0)
  
  #define PS_NULL_EVENT (0)
***************
*** 264,270 ****
  
  /* ps_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
  
! int     ps_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name)
  {
      const char *myname = "ps_dnsbl_retrieve";
      PS_DNSBL_SCORE *score;
--- 281,288 ----
  
  /* ps_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
  
! int     ps_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
!                                 int dnsbl_index)
  {
      const char *myname = "ps_dnsbl_retrieve";
      PS_DNSBL_SCORE *score;
***************
*** 278,283 ****
--- 296,306 ----
        msg_panic("%s: no blocklist score for %s", myname, client_addr);
  
      /*
+      * Disable callbacks.
+      */
+     PS_CALL_BACK_CANCEL(score, dnsbl_index);
+ 
+     /*
       * Reads are destructive.
       */
      result_score = score->total;
***************
*** 376,382 ****
  
  /* ps_dnsbl_request  - send dnsbl query, increment reference count */
  
! void    ps_dnsbl_request(const char *client_addr,
                                 void (*callback) (int, char *),
                                 char *context)
  {
--- 399,405 ----
  
  /* ps_dnsbl_request  - send dnsbl query, increment reference count */
  
! int     ps_dnsbl_request(const char *client_addr,
                                 void (*callback) (int, char *),
                                 char *context)
  {
***************
*** 420,426 ****
                     score->pending_lookups);
        if (score->pending_lookups == 0)
            event_request_timer(callback, context, EVENT_NULL_DELAY);
!       return;
      }
      if (msg_verbose > 1)
        msg_info("%s: create blocklist score for %s", myname, client_addr);
--- 443,449 ----
                     score->pending_lookups);
        if (score->pending_lookups == 0)
            event_request_timer(callback, context, EVENT_NULL_DELAY);
!       return (PS_CALL_BACK_INDEX_OF_LAST(score));
      }
      if (msg_verbose > 1)
        msg_info("%s: create blocklist score for %s", myname, client_addr);
***************
*** 458,463 ****
--- 481,487 ----
                              (char *) stream, DNSBLOG_TIMEOUT);
        score->pending_lookups += 1;
      }
+     return (PS_CALL_BACK_INDEX_OF_LAST(score));
  }
  
  /* ps_dnsbl_init - initialize */
diff -cr /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen_early.c 
src/postscreen/postscreen_early.c
*** /var/tmp/postfix-2.8-20100923/src/postscreen/postscreen_early.c     Tue Sep 
21 20:02:24 2010
--- src/postscreen/postscreen_early.c   Wed Oct  6 16:15:32 2010
***************
*** 110,116 ****
  
        if (state->flags & PS_STATE_FLAG_DNSBL_TODO) {
            dnsbl_score =
!               ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name);
            if (dnsbl_score < var_ps_dnsbl_thresh) {
                state->dnsbl_stamp = event_time() + var_ps_dnsbl_ttl;
                PS_PASS_SESSION_STATE(state, "dnsbl test",
--- 110,117 ----
  
        if (state->flags & PS_STATE_FLAG_DNSBL_TODO) {
            dnsbl_score =
!               ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
!                                 state->dnsbl_index);
            if (dnsbl_score < var_ps_dnsbl_thresh) {
                state->dnsbl_stamp = event_time() + var_ps_dnsbl_ttl;
                PS_PASS_SESSION_STATE(state, "dnsbl test",
***************
*** 166,172 ****
                          read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
            /* Avoid memory leak. */
            if (state->flags & PS_STATE_FLAG_DNSBL_TODO)
!               (void) ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name);
            /* XXX Wait for DNS replies to come in. */
            ps_hangup_event(state);
            return;
--- 167,174 ----
                          read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
            /* Avoid memory leak. */
            if (state->flags & PS_STATE_FLAG_DNSBL_TODO)
!               (void) ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
!                                        state->dnsbl_index);
            /* XXX Wait for DNS replies to come in. */
            ps_hangup_event(state);
            return;
***************
*** 180,186 ****
        case PS_ACT_DROP:
            /* Avoid memory leak. */
            if (state->flags & PS_STATE_FLAG_DNSBL_TODO)
!               (void) ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name);
            PS_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
            return;
        case PS_ACT_ENFORCE:
--- 182,189 ----
        case PS_ACT_DROP:
            /* Avoid memory leak. */
            if (state->flags & PS_STATE_FLAG_DNSBL_TODO)
!               (void) ps_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
!                                        state->dnsbl_index);
            PS_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
            return;
        case PS_ACT_ENFORCE:
***************
*** 266,273 ****
       * Run a DNS blocklist query.
       */
      if ((state->flags & PS_STATE_FLAG_DNSBL_TODO) != 0)
!       ps_dnsbl_request(state->smtp_client_addr, ps_early_dnsbl_event,
!                           (char *) state);
  
      /*
       * Wait for the client to respond or for DNS lookup to complete.
--- 269,279 ----
       * Run a DNS blocklist query.
       */
      if ((state->flags & PS_STATE_FLAG_DNSBL_TODO) != 0)
!       state->dnsbl_index =
!           ps_dnsbl_request(state->smtp_client_addr, ps_early_dnsbl_event,
!                            (char *) state);
!     else
!       state->dnsbl_index = -1;
  
      /*
       * Wait for the client to respond or for DNS lookup to complete.

Reply via email to