Do you have a ticket open on bugs.digium.com for this? If so could you
let me have the number so I can monitor it, if not would you mind opening
one? This could really do with review from people with good understanding
of the Asterisk code.


On 2015/01/29 18:04, Ed Hynan wrote:
> Asterisk does its own TZ time formatting rather than using the
> C library functions.  This seems to be so that it can watch the
> zoneinfo files for changes (TZ data updates are not very uncommon
> on GNU/Linux systems).  To implement file watching there is
> cpp conditional code for Linux inotify, else for kqueue(2), else
> sleep-and-stat code.  The file watch code runs in a thread.
> 
> There are a few bugs in the time formatting code.  One in particular
> results from the fact that kqueue is used with open file descriptors
> and some non-fatal errors can cause a proc tzparse() to be called
> repeatedly, and tzparse() unconditionally causes a zoneinfo file
> (/usr/share/zoneinfo/posixrules) to be opened and added to the kqueue
> each time, and these descriptors are not associated the state
> structs that the code maintains.  Needless to say, I got into this
> code when a call failed and I found Asterisk was out of files.
> 
> Next problem is: why the non-fatal errors mentioned above that
> trigger the leak?  Seems to be that a macro that removes list
> items from the head of a state list fails to NULL pointers when
> the last item is removed.
> 
> There are a couple more misc. bugs addressed in the patch I am
> appending.
> 
> The patch is against 11.15.0, and if requested I can post a patch
> against 11.7.0 (5.5 ports), or Asterisk svn trunk of a couple days ago.
> 
> The patch might appear to be larger than it would seem if it were
> smaller than it is.  It guards against reopening posixrules, adds
> {de,}allocators for the structs that hold descriptors for the queue,
> initializes the (list) empty head, and misc.
> 
> -Ed
> 
> 
> --- main/stdtime/localtime.c.orig     Thu Jan 29 15:05:25 2015
> +++ main/stdtime/localtime.c  Thu Jan 29 15:25:35 2015
> @@ -253,6 +253,9 @@
>                               int doextend));
>  static int           tzparse P((const char * name, struct state * sp,
>                               int lastditch));
> +/* struct state allocator with additional setup as needed */
> +static struct state *        sstate_alloc(void);
> +static void          sstate_free(struct state *p);
> 
>  static AST_LIST_HEAD_STATIC(zonelist, state);
>  #ifdef HAVE_NEWLOCALE
> @@ -282,6 +285,20 @@
>  #ifdef HAVE_INOTIFY
>  static int inotify_fd = -1;
> 
> +/* extra initialisation for sstate_alloc() */
> +#define SP_INIT_EX(sp) do { \
> +     (sp)->wd[0] = -1; \
> +     (sp)->wd[1] = -1; \
> +} while (0)
> +#if 0 /* untested: inotify users can try if they wish */
> +#define SP_FREE_EX(sp) do { \
> +     if ((sp)->wd[0] > -1) { inotify_rm_watch(inotify_fd, (sp)->wd[0]); 
> (sp)->wd[0] = -1; } \
> +     if ((sp)->wd[1] > -1) { inotify_rm_watch(inotify_fd, (sp)->wd[1]); 
> (sp)->wd[1] = -1; } \
> +} while (0)
> +#else /* this results in no change */
> +#define SP_FREE_EX(sp) do {;} while (0)
> +#endif
> +
>  static void *inotify_daemon(void *data)
>  {
>       struct {
> @@ -327,7 +344,7 @@
>               AST_LIST_TRAVERSE_SAFE_BEGIN(&zonelist, cur, list) {
>                       if (cur->wd[0] == buf.iev.wd || cur->wd[1] == 
> buf.iev.wd) {
>                               AST_LIST_REMOVE_CURRENT(list);
> -                             ast_free(cur);
> +                             sstate_free(cur);
>                               break;
>                       }
>               }
> @@ -376,14 +393,60 @@
>  #elif defined(HAVE_KQUEUE)
>  static int queue_fd = -1;
> 
> +/*
> + * static struct state *psx_sp and associated code will guard againt
> + * add_notify() called repeatedly for /usr/share/zoneinfo/posixrules
> + * and leaking a desciptor each time as a result of some errors
> + * (any code where tzparse() is called if tzload() fails --
> + * tzparse() re-calls tzload() for /usr/share/zoneinfo/posixrules)
> + */
> +static struct state *psx_sp = NULL;
> +/*
> + * kqueue_daemon_freestate() called from kqueue_daemon() --
> + * replaces resource release code that was in line there
> + */
> +static void kqueue_daemon_freestate(struct state *);
> +
> +/* collect EVFILT_VNODE fflags in macro;
> + * RENAME and LINK and TRUNCATE added */
> +#ifdef NOTE_TRUNCATE
> +#    define EVVN_NOTES_BITS \
> +     (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND|NOTE_REVOKE|NOTE_ATTRIB \
> +     |NOTE_RENAME|NOTE_LINK|NOTE_TRUNCATE)
> +#else
> +#    define EVVN_NOTES_BITS \
> +     (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND|NOTE_REVOKE|NOTE_ATTRIB \
> +     |NOTE_RENAME|NOTE_LINK)
> +#endif
> +
> +/* extra initialisation for sstate_alloc() */
> +#ifdef HAVE_O_SYMLINK
> +#define SP_INIT_EX(sp) do { \
> +     (sp)->fd = -1; \
> +     (sp)->fds = -1; \
> +} while (0)
> +#define SP_FREE_EX(sp) do { \
> +     if ((sp)->fd > -1) { close((sp)->fd); (sp)->fd = -1; } \
> +     if ((sp)->fds > -1) { close((sp)->fds); (sp)->fds = -1; } \
> +} while (0)
> +#else
> +#define SP_INIT_EX(sp) do { \
> +     (sp)->fd = -1; \
> +     (sp)->dir = NULL; \
> +} while (0)
> +#define SP_FREE_EX(sp) do { \
> +     if ((sp)->fd > -1) { close((sp)->fd); (sp)->fd = -1; } \
> +     if ((sp)->dir != NULL) { closedir((sp)->dir); (sp)->dir = NULL; } \
> +} while (0)
> +#endif
> +
>  static void *kqueue_daemon(void *data)
>  {
>       struct kevent kev;
>       struct state *sp;
> -     struct timespec no_wait = { 0, 1 };
> 
>       ast_mutex_lock(&initialization_lock);
> -     if ((queue_fd = kqueue()) < 0) {
> +     if (queue_fd < 0 && (queue_fd = kqueue()) < 0) {
>               /* ast_log uses us to format messages, so if we called ast_log, 
> we'd be
>                * in for a nasty loop (seen already in testing) */
>               fprintf(stderr, "Unable to initialize kqueue(): %s\n", 
> strerror(errno));
> @@ -410,56 +473,106 @@
> 
>               sp = kev.udata;
> 
> -             /*!\note
> -              * If the file event fired, then the file was removed, so we'll 
> need
> -              * to reparse the entry.  The directory event is a bit more
> -              * interesting.  Unfortunately, the queue doesn't contain 
> information
> -              * about the file that changed (only the directory itself), so 
> unless
> -              * we kept a record of the directory state before, it's not 
> really
> -              * possible to know what change occurred.  But if we act 
> paranoid and
> -              * just purge the associated file, then it will get reparsed, 
> and
> -              * everything works fine.  It may be more work, but it's a vast
> -              * improvement over the alternative implementation, which is to 
> stat
> -              * the file repeatedly in what is essentially a busy loop. */
> +             /* see comment near psx_sp in add_notify() */
> +             if (sp == psx_sp) {
> +                     AST_LIST_LOCK(&zonelist);
> +                     psx_sp = NULL;
> +                     fprintf(stderr,
> +                             "kevent %#x on default TZ rules %s\n",
> +                             (unsigned int)kev.fflags, sp->name);
> +
> +                     kqueue_daemon_freestate(sp);
> +
> +                     while ((sp = AST_LIST_REMOVE_HEAD(&zonelist, list))) {
> +                             kqueue_daemon_freestate(sp);
> +                     }
> +
> +                     AST_LIST_HEAD_INIT_NOLOCK(&zonelist);
> +                     AST_LIST_UNLOCK(&zonelist);
> +             } else {
> +                     AST_LIST_LOCK(&zonelist);
> +                     AST_LIST_REMOVE(&zonelist, sp, list);
> +                     kqueue_daemon_freestate(sp);
> +                     AST_LIST_UNLOCK(&zonelist);
> +             }
> +
> +             /* Just in case the signal was sent late */
>               AST_LIST_LOCK(&zonelist);
> -             AST_LIST_REMOVE(&zonelist, sp, list);
> +             ast_cond_broadcast(&initialization);
>               AST_LIST_UNLOCK(&zonelist);
> +     }
> 
> +     inotify_thread = AST_PTHREADT_NULL;
> +     return NULL;
> +}
> +
> +static void kqueue_daemon_freestate(struct state *sp)
> +{
> +     struct kevent kev;
> +     struct timespec no_wait = { 0, 1 };
> +
> +     /*!\note
> +      * If the file event fired, then the file was removed, so we'll need
> +      * to reparse the entry.  The directory event is a bit more
> +      * interesting.  Unfortunately, the queue doesn't contain information
> +      * about the file that changed (only the directory itself), so unless
> +      * we kept a record of the directory state before, it's not really
> +      * possible to know what change occurred.  But if we act paranoid and
> +      * just purge the associated file, then it will get reparsed, and
> +      * everything works fine.  It may be more work, but it's a vast
> +      * improvement over the alternative implementation, which is to stat
> +      * the file repeatedly in what is essentially a busy loop. */
> +
> +     if (sp->fd > -1) {
>               /* If the directory event fired, remove the file event */
>               EV_SET(&kev, sp->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
>               kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
> -             close(sp->fd);
> +     }
> 
>  #ifdef HAVE_O_SYMLINK
> -             if (sp->fds > -1) {
> -                     /* If the file event fired, remove the symlink event */
> -                     EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_DELETE, 0, 0, 
> NULL);
> -                     kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
> -                     close(sp->fds);
> -             }
> +     if (sp->fds > -1) {
> +             /* If the file event fired, remove the symlink event */
> +             EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
> +             kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
> +     }
>  #else
> -             if (sp->dir) {
> -                     /* If the file event fired, remove the directory event 
> */
> -                     EV_SET(&kev, dirfd(sp->dir), EVFILT_VNODE, EV_DELETE, 
> 0, 0, NULL);
> -                     kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
> -                     closedir(sp->dir);
> -             }
> +     if (sp->dir) {
> +             /* If the file event fired, remove the directory event */
> +             EV_SET(&kev, dirfd(sp->dir), EVFILT_VNODE, EV_DELETE, 0, 0, 
> NULL);
> +             kevent(queue_fd, &kev, 1, NULL, 0, &no_wait);
> +     }
>  #endif
> -             ast_free(sp);
> 
> -             /* Just in case the signal was sent late */
> -             AST_LIST_LOCK(&zonelist);
> -             ast_cond_broadcast(&initialization);
> -             AST_LIST_UNLOCK(&zonelist);
> -     }
> +     sstate_free(sp);
>  }
> 
>  static void add_notify(struct state *sp, const char *path)
>  {
>       struct kevent kev;
>       struct timespec no_wait = { 0, 1 };
> -     char watchdir[PATH_MAX + 1] = "";
> +     char   watchdir[PATH_MAX + 1] = "";
> 
> +     /* this block guards against a result of a failure return
> +      * from tzload() in several procs, in which tzparse() is then
> +      * called, and that calls tzload() again, unconditionally,
> +      * for TZDEFRULES (e.g., /usr/share/zoneinfo/posixrules);
> +      * if errors repeat, each call open(2)s and watches and a
> +      * descriptor leak is the result. So, make sure only one
> +      * 'watch' is used (and if it gets an event, all state are
> +      * freed).
> +      */
> +     if (! strcmp(path, TZDEFRULES)) {
> +             if (psx_sp != NULL ||
> +                (psx_sp = sstate_alloc()) == NULL) {
> +                     return;
> +             }
> +             ast_copy_string(psx_sp->name, path,
> +                     sizeof(psx_sp->name));
> +             sp = psx_sp;
> +     } else if (sp->fd != -1) {
> +             return;
> +     }
> +
>       if (inotify_thread == AST_PTHREADT_NULL) {
>               ast_cond_init(&initialization, NULL);
>               ast_mutex_init(&initialization_lock);
> @@ -482,7 +595,8 @@
>                       | O_EVTONLY
>  # endif
>                       )) >= 0) {
> -             EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_ADD | EV_ENABLE | 
> EV_ONESHOT, NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_REVOKE | 
> NOTE_ATTRIB, 0, sp);
> +             EV_SET(&kev, sp->fds, EVFILT_VNODE, EV_ADD | EV_ENABLE | 
> EV_ONESHOT, EVVN_NOTES_BITS, 0, sp);
> +             errno = 0;
>               if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno 
> != 0) {
>                       /* According to the API docs, we may get -1 return 
> value, due to the
>                        * NULL space for a returned event, but errno should be 
> 0 unless
> @@ -518,7 +632,8 @@
>                * Likewise, there's no potential leak of a descriptor.
>                */
>               EV_SET(&kev, dirfd(sp->dir), EVFILT_VNODE, EV_ADD | EV_ENABLE | 
> EV_ONESHOT,
> -                             NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | 
> NOTE_REVOKE | NOTE_ATTRIB, 0, sp);
> +                             EVVN_NOTES_BITS, 0, sp);
> +             errno = 0;
>               if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno 
> != 0) {
>                       fprintf(stderr, "Unable to watch '%s': %s\n", watchdir, 
> strerror(errno));
>                       closedir(sp->dir);
> @@ -538,7 +653,8 @@
>               return;
>       }
> 
> -     EV_SET(&kev, sp->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT, 
> NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_REVOKE | NOTE_ATTRIB, 0, sp);
> +     EV_SET(&kev, sp->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT, 
> EVVN_NOTES_BITS, 0, sp);
> +     errno = 0;
>       if (kevent(queue_fd, &kev, 1, NULL, 0, &no_wait) < 0 && errno != 0) {
>               /* According to the API docs, we may get -1 return value, due 
> to the
>                * NULL space for a returned event, but errno should be 0 unless
> @@ -550,6 +666,11 @@
>       }
>  }
>  #else
> +
> +/* extra initialisation for sstate_alloc() */
> +#define SP_INIT_EX(sp) do {;} while (0)
> +#define SP_FREE_EX(sp) do {;} while (0)
> +
>  static void *notify_daemon(void *data)
>  {
>       struct stat st, lst;
> @@ -589,7 +710,7 @@
>                                       ast_log(LOG_NOTICE, "Removing cached TZ 
> entry '%s' because underlying file changed.\n", name);
>                               }
>                               AST_LIST_REMOVE_CURRENT(list);
> -                             ast_free(cur);
> +                             sstate_free(cur);
>                               continue;
>                       }
>               }
> @@ -623,6 +744,28 @@
>  }
>  #endif
> 
> +/*
> + * struct state allocator with additional setup as needed
> + */
> +static struct state *sstate_alloc(void)
> +{
> +     struct state *p;
> +
> +     if ((p = ast_calloc(1, sizeof(*p))) != NULL) {
> +             SP_INIT_EX(p);
> +     }
> +
> +     return p;
> +}
> +
> +static void sstate_free(struct state *p)
> +{
> +     if (p != NULL) {
> +             SP_FREE_EX(p);
> +             ast_free(p);
> +     }
> +}
> +
>  void ast_localtime_wakeup_monitor(struct ast_test *info)
>  {
>       if (inotify_thread != AST_PTHREADT_NULL) {
> @@ -737,7 +880,9 @@
>               }
>       }
>       nread = read(fid, u.buf, sizeof u.buf);
> -     if (close(fid) < 0 || nread <= 0)
> +     /* comp was nread <= 0;
> +        use nread < sizeof u.tzhead against unexpected short files */
> +     if (close(fid) < 0 || nread < sizeof u.tzhead)
>               return -1;
>       for (stored = 4; stored <= 8; stored *= 2) {
>               int             ttisstdcnt;
> @@ -864,6 +1009,11 @@
>               nread -= p - u.buf;
>               for (i = 0; i < nread; ++i)
>                       u.buf[i] = p[i];
> +             /* next loop iter. will assume at least
> +                sizeof(struct tzhead) bytes */
> +             if (nread < sizeof(u.tzhead)) {
> +                     break;
> +             }
>               /*
>               ** If this is a narrow integer time_t system, we're done.
>               */
> @@ -1443,8 +1593,9 @@
> 
>       AST_LIST_LOCK(&zonelist);
>       while ((sp = AST_LIST_REMOVE_HEAD(&zonelist, list))) {
> -             ast_free(sp);
> +             sstate_free(sp);
>       }
> +     AST_LIST_HEAD_INIT_NOLOCK(&zonelist);
>       AST_LIST_UNLOCK(&zonelist);
>  }
> 
> @@ -1472,7 +1623,7 @@
>       }
>       AST_LIST_UNLOCK(&zonelist);
> 
> -     if (!(sp = ast_calloc(1, sizeof *sp)))
> +     if (!(sp = sstate_alloc()))
>               return NULL;
> 
>       if (tzload(zone, sp, TRUE) != 0) {
> @@ -1724,8 +1875,10 @@
>       }
> 
>       if (!sp) {
> -             if (!(sp = (struct state *) ast_calloc(1, sizeof *sp)))
> +             if (!(sp = sstate_alloc())) {
> +                     AST_LIST_UNLOCK(&zonelist);
>                       return NULL;
> +             }
>               gmtload(sp);
>               AST_LIST_INSERT_TAIL(&zonelist, sp, list);
>       }
> @@ -2304,6 +2457,7 @@
>       long fraction;
>       const char *prevlocale;
> 
> +     buf[0] = '\0';/* Ensure the buffer is initialized. */
>       if (!format) {
>               return -1;
>       }
> 

Reply via email to