Подробности

[В начало]

Проблема в реализации № S0683

Краткое описание

При сохранении изображения в формате "ico", под AND-mask'у выделяется недостаточно памяти

Подробное описание

В файле формата "ico" помимо обычного bitmap'а, содержащего информацию о пискелах изображения (т.н XOR-маска), есть bitmap (т.н AND-маска), содержащий краткую информацию о прозрачности пикселов. В последнем bitmap'е для каждого пиксела изображения отводится один бит.

Судя по коду функции (файл gtk+-2.14.4/gdk-pixbuf/io-ico.c)

static gboolean fill_entry (IconEntry *icon,
GdkPixbuf *pixbuf, gint hot_x, gint hot_y, GError **error)

размер строки AND-маски устанавливается равным (width / 8), где width - ширина исходного изображения. Производится только выравнивание на 4 байта:

icon->and_rowstride = icon->width / 8;
if ((icon->and_rowstride % 4) != 0) 		
    icon->and_rowstride = 4 * ((icon->and_rowstride / 4) + 1);
icon->and = g_new0 (guchar, icon->and_rowstride * icon->height);

Соответственно, при ширине изображения, не кратной 8, будет запись за границу строки. А для последней строки - выход за границу выделенной памяти.

Через функцию fill_entry реализованы следующие функции из gdk-pixbuf:

  • gdk_pixbuf_save
  • gdk_pixbuf_savev
  • Приведенный пример показывает возможные последствия некорректного выделения памяти в функции gdk_pixbuf_save - от неверной интерпретации (посредством gdk_pixbuf_new_from_file) сохраненного "ico" файла до segmentation fault.

    Раздел стандарта

    Linux Standard Base Desktop Specification 3.2, section 15.2.1.1 - "Interfaces for GTK General purpose utility library", который ссылается на Gdk-pixbuf 2.6.2 API Reference, File Saving

    Пример

    #include <gdk-pixbuf/gdk-pixbuf.h>
    
    #define WIDTH 33
    #define HEIGHT 33
    /*
     * Another combinations of WIDTH and HEIGHT
     * on particular system (Ubuntu 7.04 on x86):
     * 31*31, 29*31 - everything seems to be correct
     *   (and-rowstride increased due to alignment at 4 byte boundary).
     * 33*31 - Segmentation fault
     * 35*33 - free(): invalid next size (normal)
     */
    
    #define DEPTH "24"
    
    unsigned char get_transparency(int x, int y)
    {
        //"striped" image
        return (x & 4) ? 255 : 0;
    }
    int main()
    {
        g_type_init();
        GError *error = NULL;
        // Create new pixbuf,
        GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
            TRUE, 8, WIDTH, HEIGHT);
        // fill pixbuf,
        {
            unsigned char* pixels = gdk_pixbuf_get_pixels(pixbuf);
            int rowstride = gdk_pixbuf_get_rowstride(pixbuf);
            int x = 0;
            for(; x < WIDTH; x++)
            {
                int y = 0;
                for(; y < HEIGHT; y++)
                {
                    unsigned char* pixel = 
                        &pixels[y * rowstride + x * 4];
                    //monochrome black
                    pixel[0] = 0x0;//red
                    pixel[1] = 0x0;//green
                    pixel[2] = 0x0;//blue
                    //but striped by transparency
                    pixel[3] =  
                        get_transparency(x, y);//alpha
                }
            }
        }    
    
        // save as "ico",        
        // this call to gdk_pixbuf_save() causes problems
        gdk_pixbuf_save(pixbuf, "image1.ico", "ico", NULL,
            "depth", DEPTH, (char*)NULL);
    
        // and save as "png".
        gdk_pixbuf_save(pixbuf, "image1.png", "png", NULL,
            (char*)NULL);        
        
        g_object_unref(pixbuf);
        
        // Load pixbuf from "ico" file,
        GdkPixbuf *pixbuf1 = 
            gdk_pixbuf_new_from_file("image1.ico", &error);
        if(pixbuf1 == NULL)
        {
            printf("Cannot load pixbuf: %s.
    ",
               error->message);
            return 1;
        }
        // and save it as "png".
        gdk_pixbuf_save(pixbuf1,
            "image2.png", "png", NULL, (char*)NULL);
        // Verify that pixbuf remain striped
        {
            if(!gdk_pixbuf_get_has_alpha(pixbuf1))
            {
                printf("Loaded pixbuf hasn't alpha channel "
                    "- cannot verify result.
    ");
                return 2;
            }
            unsigned char* pixels = 
                gdk_pixbuf_get_pixels(pixbuf1);
            int rowstride = 
                gdk_pixbuf_get_rowstride(pixbuf1);
            int x = 0;
            for(; x < WIDTH; x++)
            {
                int y = 0;
                for(; y < HEIGHT; y++)
                {
                    unsigned char* pixel = 
                        &pixels[y * rowstride + x * 4];
                    //verify transparency
                    unsigned char et = 
                        get_transparency(x, y); 
                    if(((et >= 128) && (pixel[3] < 128))
                     || ((et < 128) && (pixel[3] >= 128)))
                    {
                        printf("Transparency of pixel "
                            "at (%d, %d) was %d,
    ", x, y,
                            et);
                        printf("but became %d.
    ", pixel[3]);
                        break;
                    }
                }
                if(y != HEIGHT) break;
            }
            if(x == WIDTH) printf("Transparency remains correct.
    ");
        }    
        
        g_object_unref(pixbuf1);
        
        return 0;
    }

    Компонент

    gtk-gdk-pixbuf 2.6.2 or later

    Принято

    Gnome Bugzilla 561669

    Статус

    Исправлено в gtk-gdk-pixbuf 2.19.1

    [В начало]