CVE-2020-15861: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: Net-SNMP through 5.7.3 allows Escalation of Privileges because of UNIX symbolic link (symlink) following.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
CVSS v3 base score: 7.8
Published: 2020-08-20T01:17Z
Repository: https://github.com/net-snmp/net-snmp
Language: C
Files changed: .gitignore, include/net-snmp/library/mib.h, include/net-snmp/library/parse.h, snmplib/mib.c, snmplib/parse.c
Commit date: 2019-07-03 20:46:30 -0700
Commit message: CHANGES: snmpd: Stop reading and writing the mib_indexes/* files

Caching directory contents is something the operating system should do
and is not something Net-SNMP should do. Instead of storing a copy of
the directory contents in ${tmp_dir}/mib_indexes/${n}, always scan a
MIB directory.

Diff: diff --git a/.gitignore b/.gitignore
index 2d37bc61c..94da5688f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,11 +73,10 @@ local/snmpcheck
 local/snmpconf
 Makefile
 man/*.[1358]
 man/default_store.3.h
 man/manaliases
-mibs/.index
 mk/
 module_tmp_header.h
 net-snmp-5*
 net-snmp-6*
 net-snmp-config
diff --git a/include/net-snmp/library/mib.h b/include/net-snmp/library/mib.h
index ab36853b9..3e8163496 100644
--- a/include/net-snmp/library/mib.h
+++ b/include/net-snmp/library/mib.h
@@ -122,13 +122,10 @@ SOFTWARE.
     void            register_mib_handlers(void);
     void            netsnmp_set_mib_directory(const char *dir);
     NETSNMP_IMPORT
     char            *netsnmp_get_mib_directory(void);
     void            netsnmp_fixup_mib_directory(void);
-    void            netsnmp_mibindex_load( void );
-    char *          netsnmp_mibindex_lookup( const char * );
-    FILE *          netsnmp_mibindex_new( const char * );
     int             sprint_realloc_description(u_char ** buf, size_t * buf_len,
                                 size_t * out_len, int allow_realloc,
                                 oid * objid, size_t objidlen, int width);
     NETSNMP_IMPORT
     int             get_wild_node(const char *, oid *, size_t *);
diff --git a/include/net-snmp/library/parse.h b/include/net-snmp/library/parse.h
index ce46ab95a..7c33d3f45 100644
--- a/include/net-snmp/library/parse.h
+++ b/include/net-snmp/library/parse.h
@@ -199,11 +199,11 @@ SOFTWARE.
 #ifndef NETSNMP_NO_LEGACY_DEFINITIONS
     int             unload_module(const char *name);
 #endif
     void            netsnmp_init_mib_internals(void);
     void            unload_all_mibs(void);
-    int             add_mibfile(const char*, const char*, FILE *);
+    int             add_mibfile(const char*, const char*);
     int             which_module(const char *);
     NETSNMP_IMPORT
     char           *module_name(int, char *);
     NETSNMP_IMPORT
     void            print_subtree(FILE *, struct tree *, int);
diff --git a/snmplib/mib.c b/snmplib/mib.c
index 5f1dfbb61..37e5baba7 100644
--- a/snmplib/mib.c
+++ b/snmplib/mib.c
@@ -2718,11 +2718,10 @@ netsnmp_init_mib(void)
      */
     netsnmp_fixup_mib_directory();
     env_var = strdup(netsnmp_get_mib_directory());
     if (!env_var)
         return;
-    netsnmp_mibindex_load();
 
     DEBUGMSGTL(("init_mib",
                 "Seen MIBDIRS: Looking in '%s' for mib dirs ...\n",
                 env_var));
 
@@ -2738,11 +2737,11 @@ netsnmp_init_mib(void)
         if (*env_var == '+')
             entry = strtok_r(env_var+1, ENV_SEPARATOR, &st);
         else
             entry = strtok_r(env_var, ENV_SEPARATOR, &st);
         while (entry) {
-            add_mibfile(entry, NULL, NULL);
+            add_mibfile(entry, NULL);
             entry = strtok_r(NULL, ENV_SEPARATOR, &st);
         }
     }
 
     netsnmp_init_mib_internals();
@@ -2889,146 +2888,10 @@ init_mib(void)
     netsnmp_init_mib();
 }
 #endif
 
 
-/*
- * Handle MIB indexes centrally
- */
-static int _mibindex     = 0;   /* Last index in use */
-static int _mibindex_max = 0;   /* Size of index array */
-char     **_mibindexes   = NULL;
-
-int _mibindex_add( const char *dirname, int i );
-void
-netsnmp_mibindex_load( void )
-{
-    DIR *dir;
-    struct dirent *file;
-    FILE *fp;
-    char tmpbuf[ 300];
-    char tmpbuf2[300];
-    int  i;
-    char *cp;
-
-    /*
-     * Open the MIB index directory, or create it (empty)
-     */
-    snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes",
-              get_persistent_directory());
-    tmpbuf[sizeof(tmpbuf)-1] = 0;
-    dir = opendir( tmpbuf );
-    if ( dir == NULL ) {
-        DEBUGMSGTL(("mibindex", "load: (new)\n"));
-        mkdirhier( tmpbuf, NETSNMP_AGENT_DIRECTORY_MODE, 0);
-        return;
-    }
-
-    /*
-     * Create a list of which directory each file refers to
-     */
-    while ((file = readdir( dir ))) {
-        if ( !isdigit((unsigned char)(file->d_name[0])))
-            continue;
-        i = atoi( file->d_name );
-
-        snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
-              get_persistent_directory(), i );
-        tmpbuf[sizeof(tmpbuf)-1] = 0;
-        fp = fopen( tmpbuf, "r" );
-        if (!fp)
-            continue;
-        cp = fgets( tmpbuf2, sizeof(tmpbuf2), fp );
-        fclose( fp );
-        if ( !cp ) {
-            DEBUGMSGTL(("mibindex", "Empty MIB index (%d)\n", i));
-            continue;
-        }
-        if ( strncmp( tmpbuf2, "DIR ", 4 ) != 0 ) {
-            DEBUGMSGTL(("mibindex", "Malformed MIB index (%d)\n", i));
-            continue;
-        }
-        tmpbuf2[strlen(tmpbuf2)-1] = 0;
-        DEBUGMSGTL(("mibindex", "load: (%d) %s\n", i, tmpbuf2));
-        (void)_mibindex_add( tmpbuf2+4, i );  /* Skip 'DIR ' */
-    }
-    closedir( dir );
-}
-
-char *
-netsnmp_mibindex_lookup( const char *dirname )
-{
-    int i;
-    static char tmpbuf[300];
-
-    for (i=0; i<_mibindex; i++) {
-        if ( _mibindexes[i] &&
-             strcmp( _mibindexes[i], dirname ) == 0) {
-             snprintf(tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
-                      get_persistent_directory(), i);
-             tmpbuf[sizeof(tmpbuf)-1] = 0;
-             DEBUGMSGTL(("mibindex", "lookup: %s (%d) %s\n", dirname, i, tmpbuf ));
-             return tmpbuf;
-        }
-    }
-    DEBUGMSGTL(("mibindex", "lookup: (none)\n"));
-    return NULL;
-}
-
-int
-_mibindex_add( const char *dirname, int i )
-{
-    const int old_mibindex_max = _mibindex_max;
-
-    DEBUGMSGTL(("mibindex", "add: %s (%d)\n", dirname, i ));
-    if ( i == -1 )
-        i = _mibindex++;
-    if ( i >= _mibindex_max ) {
-        /*
-         * If the index array is full (or non-existent)
-         *   then expand (or create) it
-         */
-        _mibindex_max = i + 10;
-        _mibindexes = realloc(_mibindexes,
-                              _mibindex_max * sizeof(_mibindexes[0]));
-        netsnmp_assert(_mibindexes);
-        memset(_mibindexes + old_mibindex_max, 0,
-               (_mibindex_max - old_mibindex_max) * sizeof(_mibindexes[0]));
-    }
-
-    _mibindexes[ i ] = strdup( dirname );
-    if ( i >= _mibindex )
-        _mibindex = i+1;
-
-    DEBUGMSGTL(("mibindex", "add: %d/%d/%d\n", i, _mibindex, _mibindex_max ));
-    return i;
-}
-    
-FILE *
-netsnmp_mibindex_new( const char *dirname )
-{
-    FILE *fp;
-    char  tmpbuf[300];
-    char *cp;
-    int   i;
-
-    cp = netsnmp_mibindex_lookup( dirname );
-    if (!cp) {
-        i  = _mibindex_add( dirname, -1 );
-        snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
-                  get_persistent_directory(), i );
-        tmpbuf[sizeof(tmpbuf)-1] = 0;
-        cp = tmpbuf;
-    }
-    DEBUGMSGTL(("mibindex", "new: %s (%s)\n", dirname, cp ));
-    fp = fopen( cp, "w" );
-    if (fp)
-        fprintf( fp, "DIR %s\n", dirname );
-    return fp;
-}
-
-
 /**
  * Unloads all mibs.
  */
 void
 shutdown_mib(void)
@@ -3039,19 +2902,10 @@ shutdown_mib(void)
             SNMP_FREE(tree_top->label);
         SNMP_FREE(tree_top);
     }
     tree_head = NULL;
     Mib = NULL;
-    if (_mibindexes) {
-        int i;
-        for (i = 0; i < _mibindex; ++i)
-            SNMP_FREE(_mibindexes[i]);
-        free(_mibindexes);
-        _mibindex = 0;
-        _mibindex_max = 0;
-        _mibindexes = NULL;
-    }
     if (Prefix != NULL && Prefix != &Standard_Prefix[0])
         SNMP_FREE(Prefix);
     if (Prefix)
         Prefix = NULL;
     SNMP_FREE(confmibs);
diff --git a/snmplib/parse.c b/snmplib/parse.c
index 7f985423c..58d777e25 100644
--- a/snmplib/parse.c
+++ b/snmplib/parse.c
@@ -605,12 +605,10 @@ static struct node *parse(FILE *, struct node *);
 static int     read_module_internal(const char *);
 static int     read_module_replacements(const char *);
 static int     read_import_replacements(const char *,
                                          struct module_import *);
 
-static void     new_module(const char *, const char *);
-
 static struct node *merge_parse_objectid(struct node *, FILE *, char *);
 static struct index_list *getIndexes(FILE * fp, struct index_list **);
 static struct varbind_list *getVarbinds(FILE * fp, struct varbind_list **);
 static void     free_indexes(struct index_list **);
 static void     free_varbinds(struct varbind_list **);
@@ -4857,11 +4855,11 @@ snmp_get_token(FILE * fp, char *token, int maxtlen)
     return get_token(fp, token, maxtlen);
 }
 #endif /* NETSNMP_FEATURE_REMOVE_PARSE_GET_TOKEN */
 
 int
-add_mibfile(const char* tmpstr, const char* d_name, FILE *ip )
+add_mibfile(const char* tmpstr, const char* d_name)
 {
     FILE           *fp;
     char            token[MAXTOKEN], token2[MAXTOKEN];
 
     /*
@@ -4882,12 +4880,10 @@ add_mibfile(const char* tmpstr, const char* d_name, FILE *ip )
     /*
      * simple test for this being a MIB 
      */
     if (get_token(fp, token2, MAXTOKEN) == DEFINITIONS) {
         new_module(token, tmpstr);
-        if (ip)
-            fprintf(ip, "%s %s\n", token, d_name);
         fclose(fp);
         return 0;
     } else {
         fclose(fp);
         return 1;
@@ -4975,75 +4971,26 @@ static int scan_directory(char ***result, const char *dirname)
  * in the add_mibdir function
  */
 int
 add_mibdir(const char *dirname)
 {
-    FILE           *ip;
     const char     *oldFile = File;
     char          **filenames;
     int             count = 0;
     int             filename_count, i;
-#if !(defined(WIN32) || defined(cygwin))
-    char           *token;
-    char space;
-    char newline;
-    struct stat     dir_stat, idx_stat;
-    char            tmpstr[300];
-    char            tmpstr1[300];
-#endif
 
     DEBUGMSGTL(("parse-mibs", "Scanning directory %s\n", dirname));
-#if !(defined(WIN32) || defined(cygwin))
-    token = netsnmp_mibindex_lookup( dirname );
-    if (token && stat(token, &idx_stat) == 0 && stat(dirname, &dir_stat) == 0) {
-        if (dir_stat.st_mtime < idx_stat.st_mtime) {
-            DEBUGMSGTL(("parse-mibs", "The index is good\n"));
-            if ((ip = fopen(token, "r")) != NULL) {
-                fgets(tmpstr, sizeof(tmpstr), ip); /* Skip dir line */
-                while (fscanf(ip, "%127s%c%299[^\n]%c", token, &space, tmpstr,
-		    &newline) == 4) {
-
-		    /*
-		     * If an overflow of the token or tmpstr buffers has been
-		     * found log a message and break out of the while loop,
-		     * thus the rest of the file tokens will be ignored.
-		     */
-		    if (space != ' ' || newline != '\n') {
-			snmp_log(LOG_ERR,
-			    "add_mibdir: strings scanned in from %s/%s " \
-			    "are too large.  count = %d\n ", dirname,
-			    ".index", count);
-			    break;
-		    }
-		   
-		    snprintf(tmpstr1, sizeof(tmpstr1), "%s/%s", dirname, tmpstr);
-                    tmpstr1[ sizeof(tmpstr1)-1 ] = 0;
-                    new_module(token, tmpstr1);
-                    count++;
-                }
-                fclose(ip);
-                return count;
-            } else
-                DEBUGMSGTL(("parse-mibs", "Can't read index\n"));
-        } else
-            DEBUGMSGTL(("parse-mibs", "Index outdated\n"));
-    } else
-        DEBUGMSGTL(("parse-mibs", "No index\n"));
-#endif
 
     filename_count = scan_directory(&filenames, dirname);
 
     if (filename_count >= 0) {
-        ip = netsnmp_mibindex_new(dirname);
         for (i = 0; i < filename_count; i++) {
-            if (add_mibfile(filenames[i], strrchr(filenames[i], '/'), ip) == 0)
+            if (add_mibfile(filenames[i], strrchr(filenames[i], '/')) == 0)
                 count++;
 	    free(filenames[i]);
         }
         File = oldFile;
-        if (ip)
-            fclose(ip);
         free(filenames);
         return (count);
     }
     else
         DEBUGMSGTL(("parse-mibs","cannot open MIB directory %s\n", dirname));

Vulnerable code: mibs/.index
    void            netsnmp_mibindex_load( void );
    char *          netsnmp_mibindex_lookup( const char * );
    FILE *          netsnmp_mibindex_new( const char * );
    int             add_mibfile(const char*, const char*, FILE *);
    netsnmp_mibindex_load();
            add_mibfile(entry, NULL, NULL);
/*
 * Handle MIB indexes centrally
 */
static int _mibindex     = 0;   /* Last index in use */
static int _mibindex_max = 0;   /* Size of index array */
char     **_mibindexes   = NULL;

int _mibindex_add( const char *dirname, int i );
void
netsnmp_mibindex_load( void )
{
    DIR *dir;
    struct dirent *file;
    FILE *fp;
    char tmpbuf[ 300];
    char tmpbuf2[300];
    int  i;
    char *cp;

    /*
     * Open the MIB index directory, or create it (empty)
     */
    snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes",
              get_persistent_directory());
    tmpbuf[sizeof(tmpbuf)-1] = 0;
    dir = opendir( tmpbuf );
    if ( dir == NULL ) {
        DEBUGMSGTL(("mibindex", "load: (new)\n"));
        mkdirhier( tmpbuf, NETSNMP_AGENT_DIRECTORY_MODE, 0);
        return;
    }

    /*
     * Create a list of which directory each file refers to
     */
    while ((file = readdir( dir ))) {
        if ( !isdigit((unsigned char)(file->d_name[0])))
            continue;
        i = atoi( file->d_name );

        snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
              get_persistent_directory(), i );
        tmpbuf[sizeof(tmpbuf)-1] = 0;
        fp = fopen( tmpbuf, "r" );
        if (!fp)
            continue;
        cp = fgets( tmpbuf2, sizeof(tmpbuf2), fp );
        fclose( fp );
        if ( !cp ) {
            DEBUGMSGTL(("mibindex", "Empty MIB index (%d)\n", i));
            continue;
        }
        if ( strncmp( tmpbuf2, "DIR ", 4 ) != 0 ) {
            DEBUGMSGTL(("mibindex", "Malformed MIB index (%d)\n", i));
            continue;
        }
        tmpbuf2[strlen(tmpbuf2)-1] = 0;
        DEBUGMSGTL(("mibindex", "load: (%d) %s\n", i, tmpbuf2));
        (void)_mibindex_add( tmpbuf2+4, i );  /* Skip 'DIR ' */
    }
    closedir( dir );
}

char *
netsnmp_mibindex_lookup( const char *dirname )
{
    int i;
    static char tmpbuf[300];

    for (i=0; i<_mibindex; i++) {
        if ( _mibindexes[i] &&
             strcmp( _mibindexes[i], dirname ) == 0) {
             snprintf(tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
                      get_persistent_directory(), i);
             tmpbuf[sizeof(tmpbuf)-1] = 0;
             DEBUGMSGTL(("mibindex", "lookup: %s (%d) %s\n", dirname, i, tmpbuf ));
             return tmpbuf;
        }
    }
    DEBUGMSGTL(("mibindex", "lookup: (none)\n"));
    return NULL;
}

int
_mibindex_add( const char *dirname, int i )
{
    const int old_mibindex_max = _mibindex_max;

    DEBUGMSGTL(("mibindex", "add: %s (%d)\n", dirname, i ));
    if ( i == -1 )
        i = _mibindex++;
    if ( i >= _mibindex_max ) {
        /*
         * If the index array is full (or non-existent)
         *   then expand (or create) it
         */
        _mibindex_max = i + 10;
        _mibindexes = realloc(_mibindexes,
                              _mibindex_max * sizeof(_mibindexes[0]));
        netsnmp_assert(_mibindexes);
        memset(_mibindexes + old_mibindex_max, 0,
               (_mibindex_max - old_mibindex_max) * sizeof(_mibindexes[0]));
    }

    _mibindexes[ i ] = strdup( dirname );
    if ( i >= _mibindex )
        _mibindex = i+1;

    DEBUGMSGTL(("mibindex", "add: %d/%d/%d\n", i, _mibindex, _mibindex_max ));
    return i;
}
    
FILE *
netsnmp_mibindex_new( const char *dirname )
{
    FILE *fp;
    char  tmpbuf[300];
    char *cp;
    int   i;

    cp = netsnmp_mibindex_lookup( dirname );
    if (!cp) {
        i  = _mibindex_add( dirname, -1 );
        snprintf( tmpbuf, sizeof(tmpbuf), "%s/mib_indexes/%d",
                  get_persistent_directory(), i );
        tmpbuf[sizeof(tmpbuf)-1] = 0;
        cp = tmpbuf;
    }
    DEBUGMSGTL(("mibindex", "new: %s (%s)\n", dirname, cp ));
    fp = fopen( cp, "w" );
    if (fp)
        fprintf( fp, "DIR %s\n", dirname );
    return fp;
}


    if (_mibindexes) {
        int i;
        for (i = 0; i < _mibindex; ++i)
            SNMP_FREE(_mibindexes[i]);
        free(_mibindexes);
        _mibindex = 0;
        _mibindex_max = 0;
        _mibindexes = NULL;
    }
static void     new_module(const char *, const char *);

add_mibfile(const char* tmpstr, const char* d_name, FILE *ip )
        if (ip)
            fprintf(ip, "%s %s\n", token, d_name);
    FILE           *ip;
#if !(defined(WIN32) || defined(cygwin))
    char           *token;
    char space;
    char newline;
    struct stat     dir_stat, idx_stat;
    char            tmpstr[300];
    char            tmpstr1[300];
#endif
#if !(defined(WIN32) || defined(cygwin))
    token = netsnmp_mibindex_lookup( dirname );
    if (token && stat(token, &idx_stat) == 0 && stat(dirname, &dir_stat) == 0) {
        if (dir_stat.st_mtime < idx_stat.st_mtime) {
            DEBUGMSGTL(("parse-mibs", "The index is good\n"));
            if ((ip = fopen(token, "r")) != NULL) {
                fgets(tmpstr, sizeof(tmpstr), ip); /* Skip dir line */
                while (fscanf(ip, "%127s%c%299[^\n]%c", token, &space, tmpstr,
		    &newline) == 4) {

		    /*
		     * If an overflow of the token or tmpstr buffers has been
		     * found log a message and break out of the while loop,
		     * thus the rest of the file tokens will be ignored.
		     */
		    if (space != ' ' || newline != '\n') {
			snmp_log(LOG_ERR,
			    "add_mibdir: strings scanned in from %s/%s " \
			    "are too large.  count = %d\n ", dirname,
			    ".index", count);
			    break;
		    }
		   
		    snprintf(tmpstr1, sizeof(tmpstr1), "%s/%s", dirname, tmpstr);
                    tmpstr1[ sizeof(tmpstr1)-1 ] = 0;
                    new_module(token, tmpstr1);
                    count++;
                }
                fclose(ip);
                return count;
            } else
                DEBUGMSGTL(("parse-mibs", "Can't read index\n"));
        } else
            DEBUGMSGTL(("parse-mibs", "Index outdated\n"));
    } else
        DEBUGMSGTL(("parse-mibs", "No index\n"));
#endif
        ip = netsnmp_mibindex_new(dirname);
            if (add_mibfile(filenames[i], strrchr(filenames[i], '/'), ip) == 0)
        if (ip)
            fclose(ip);
Fixed code:     int             add_mibfile(const char*, const char*);
            add_mibfile(entry, NULL);
add_mibfile(const char* tmpstr, const char* d_name)
            if (add_mibfile(filenames[i], strrchr(filenames[i], '/')) == 0)