X Strike Force XFree86 SVN commit: r1819 - in branches/4.1.0/woody/debian: . patches
Author: branden
Date: 2004-09-16 16:15:18 -0500 (Thu, 16 Sep 2004)
New Revision: 1819
Added:
branches/4.1.0/woody/debian/patches/076_SECURITY_libXpm_vulnerabilities.diff
Modified:
branches/4.1.0/woody/debian/changelog
Log:
Security update release. Resolves the following issues:
+ CAN-2004-0687: stack overflows in libXpm
+ CAN-2004-0688: integer overflows in libXpm
Modified: branches/4.1.0/woody/debian/changelog
===================================================================
--- branches/4.1.0/woody/debian/changelog 2004-09-15 11:56:07 UTC (rev 1818)
+++ branches/4.1.0/woody/debian/changelog 2004-09-16 21:15:18 UTC (rev 1819)
@@ -1,3 +1,11 @@
+xfree86 (4.1.0-16woody4) stable-security; urgency=high
+
+ * Security update release. Resolves the following issues:
+ + CAN-2004-0687: stack overflows in libXpm
+ + CAN-2004-0688: integer overflows in libXpm
+
+ -- Branden Robinson <branden@debian.org> Thu, 16 Sep 2004 16:13:56 -0500
+
xfree86 (4.1.0-16woody3) stable-security; urgency=high
* Security update release. Resolves the following issues:
Added: branches/4.1.0/woody/debian/patches/076_SECURITY_libXpm_vulnerabilities.diff
===================================================================
--- branches/4.1.0/woody/debian/patches/076_SECURITY_libXpm_vulnerabilities.diff 2004-09-15 11:56:07 UTC (rev 1818)
+++ branches/4.1.0/woody/debian/patches/076_SECURITY_libXpm_vulnerabilities.diff 2004-09-16 21:15:18 UTC (rev 1819)
@@ -0,0 +1,522 @@
+$Id$
+
+Fix several security flaws in the Xpm library. Resolves CAN-2004-0687 (libXpm
+stack overflows) and CAN-2004-0688 (libXpm integer overflows).
+
+The following text is by Chris Evans.
+
+ libXpm multiple image parsing flaws
+ ===================================
+
+ Programs affected: libXpm, and any programs which use libXpm to decode XPM
+ files. For example, the GIMP seems to use libXpm.
+
+ Severity: Compromise of account used to browse malicious XPM file.
+
+ This advisory lists code flaws discovered by inspection of the libXpm code.
+ The specific version of libXpm discussed is the release that comes with the
+ initial X.ORG X11 system source code release. However, these flaws seem to
+ exist in older versions.
+
+ Flaw 1. Stack-based overflow in xpmParseColors (parse.c).
+
+ Careless use of strcat() in both the XPMv1 and XPMv2/3 parsing code leads
+ to a stack based overflow that should be exploitable. There are minor
+ complications due to stack layout; the buffer being overflowed in fact
+ typically overflows into another buffer that is used to populate the
+ overflowed buffer. This should not prevent exploitation, however.
+
+ Flaw 2. Integer overflow allocating colorTable in xpmParseColors (parse.c)
+ -- probably a crashable but not exploitable offence. Here:
+
+ colorTable = (XpmColor *) XpmCalloc(ncolors, sizeof(XpmColor));
+
+ ncolors would seem to come from the (untrusted) XPM file.
+
+ In fact, multiple integer overflow problems seem to exist. Some may well
+ be exploitable. Note that the following may not be an exhaustive list:
+
+ a) XpmCreateImageFromXpmImage: multiple possible overflow, e.g.:
+ image_pixels = (Pixel *) XpmMalloc(sizeof(Pixel) * image->ncolors);
+ (ncolors is user-supplied)
+
+ b) CreateXImage:
+ *image_return = XCreateImage(display, visual, depth, format, 0, 0,
+ width, height, bitmap_pad, 0);
+ (width and height are user-supplied, possibly other variables too)
+
+ c) ParsePixels:
+ iptr2 = (unsigned int *) XpmMalloc(sizeof(unsigned int) * width * height);
+ (width and height are user-supplied)
+
+ d) ParseAndPutPixels and ParsePixels:
+ cidx[char1][(unsigned char)colorTable[a].string[1]] = a + 1;
+ (possibly, char1 might be negative, and access the cidx array out of bounds)
+
+ Flaw 3. Stack overflow reading pixel values in ParseAndPutPixels
+ (create.c) as well as ParsePixels (parse.c). Should be exploitable.
+
+ A user-supplied number of bytes are stuffed into a fixed-size buffer (typically
+ 8192 bytes). The user gets to choose how many bytes to put into this
+ buffer via the "number of bytes per pixel" XPM value.
+
+This patch by Matthieu Herrb.
+
+diff -urN xc/extras/Xpm~/lib/Attrib.c xc/extras/Xpm/lib/Attrib.c
+--- xc/extras/Xpm~/lib/Attrib.c 1999-01-11 08:23:09.000000000 -0500
++++ xc/extras/Xpm/lib/Attrib.c 2004-09-15 16:30:40.000000000 -0500
+@@ -35,7 +35,7 @@
+ #include "XpmI.h"
+
+ /* 3.2 backward compatibility code */
+-LFUNC(CreateOldColorTable, int, (XpmColor *ct, int ncolors,
++LFUNC(CreateOldColorTable, int, (XpmColor *ct, unsigned int ncolors,
+ XpmColor ***oldct));
+
+ LFUNC(FreeOldColorTable, void, (XpmColor **colorTable, int ncolors));
+@@ -46,12 +46,15 @@
+ static int
+ CreateOldColorTable(ct, ncolors, oldct)
+ XpmColor *ct;
+- int ncolors;
++ unsigned int ncolors;
+ XpmColor ***oldct;
+ {
+ XpmColor **colorTable, **color;
+ int a;
+
++ if (ncolors >= SIZE_MAX / sizeof(XpmColor *))
++ return XpmNoMemory;
++
+ colorTable = (XpmColor **) XpmMalloc(ncolors * sizeof(XpmColor *));
+ if (!colorTable) {
+ *oldct = NULL;
+diff -urN xc/extras/Xpm~/lib/CrDatFrI.c xc/extras/Xpm/lib/CrDatFrI.c
+--- xc/extras/Xpm~/lib/CrDatFrI.c 1999-01-11 08:23:09.000000000 -0500
++++ xc/extras/Xpm/lib/CrDatFrI.c 2004-09-15 16:30:40.000000000 -0500
+@@ -123,6 +123,8 @@
+ */
+ header_nlines = 1 + image->ncolors;
+ header_size = sizeof(char *) * header_nlines;
++ if (header_size >= SIZE_MAX / sizeof(char *))
++ return (XpmNoMemory);
+ header = (char **) XpmCalloc(header_size, sizeof(char *));
+ if (!header)
+ return (XpmNoMemory);
+diff -urN xc/extras/Xpm~/lib/WrFFrI.c xc/extras/Xpm/lib/WrFFrI.c
+--- xc/extras/Xpm~/lib/WrFFrI.c 1999-01-11 08:23:10.000000000 -0500
++++ xc/extras/Xpm/lib/WrFFrI.c 2004-09-15 16:30:40.000000000 -0500
+@@ -247,6 +247,8 @@
+ unsigned int x, y, h;
+
+ h = height - 1;
++ if (cpp != 0 && width >= (SIZE_MAX - 3)/cpp)
++ return XpmNoMemory;
+ p = buf = (char *) XpmMalloc(width * cpp + 3);
+ if (!buf)
+ return (XpmNoMemory);
+diff -urN xc/extras/Xpm~/lib/XpmI.h xc/extras/Xpm/lib/XpmI.h
+--- xc/extras/Xpm~/lib/XpmI.h 2000-09-26 10:56:42.000000000 -0500
++++ xc/extras/Xpm/lib/XpmI.h 2004-09-15 16:30:40.000000000 -0500
+@@ -116,6 +116,18 @@
+ boundCheckingCalloc((long)(nelem),(long) (elsize))
+ #endif
+
++#if defined(SCO) || defined(__USLC__)
++#include <stdint.h> /* For SIZE_MAX */
++#endif
++#include <limits.h>
++#ifndef SIZE_MAX
++# ifdef ULONG_MAX
++# define SIZE_MAX ULONG_MAX
++# else
++# define SIZE_MAX UINT_MAX
++# endif
++#endif
++
+ #define XPMMAXCMTLEN BUFSIZ
+ typedef struct {
+ unsigned int type;
+@@ -217,9 +229,9 @@
+ } *xpmHashAtom;
+
+ typedef struct {
+- int size;
+- int limit;
+- int used;
++ unsigned int size;
++ unsigned int limit;
++ unsigned int used;
+ xpmHashAtom *atomTable;
+ } xpmHashTable;
+
+diff -urN xc/extras/Xpm~/lib/create.c xc/extras/Xpm/lib/create.c
+--- xc/extras/Xpm~/lib/create.c 1999-01-11 08:23:10.000000000 -0500
++++ xc/extras/Xpm/lib/create.c 2004-09-15 16:30:40.000000000 -0500
+@@ -1,3 +1,4 @@
++/* $XdotOrg: pre-CVS proposed fix for CESA-2004-003 alanc 7/25/2004 $ */
+ /*
+ * Copyright (C) 1989-95 GROUPE BULL
+ *
+@@ -819,6 +820,9 @@
+
+ ErrorStatus = XpmSuccess;
+
++ if (image->ncolors >= SIZE_MAX / sizeof(Pixel))
++ return (XpmNoMemory);
++
+ /* malloc pixels index tables */
+ image_pixels = (Pixel *) XpmMalloc(sizeof(Pixel) * image->ncolors);
+ if (!image_pixels)
+@@ -991,6 +995,8 @@
+ return (XpmNoMemory);
+
+ #if !defined(FOR_MSW) && !defined(AMIGA)
++ if (height != 0 && (*image_return)->bytes_per_line >= SIZE_MAX / height)
++ return XpmNoMemory;
+ /* now that bytes_per_line must have been set properly alloc data */
+ (*image_return)->data =
+ (char *) XpmMalloc((*image_return)->bytes_per_line * height);
+@@ -2063,6 +2069,9 @@
+ xpmGetCmt(data, &colors_cmt);
+
+ /* malloc pixels index tables */
++ if (ncolors >= SIZE_MAX / sizeof(Pixel))
++ return XpmNoMemory;
++
+ image_pixels = (Pixel *) XpmMalloc(sizeof(Pixel) * ncolors);
+ if (!image_pixels)
+ RETURN(XpmNoMemory);
+@@ -2317,7 +2326,8 @@
+ }
+ obm = SelectObject(*dc, image->bitmap);
+ #endif
+-
++ if (ncolors > 256)
++ return (XpmFileInvalid);
+
+ bzero((char *)colidx, 256 * sizeof(short));
+ for (a = 0; a < ncolors; a++)
+@@ -2423,6 +2433,9 @@
+ char *s;
+ char buf[BUFSIZ];
+
++ if (cpp >= sizeof(buf))
++ return (XpmFileInvalid);
++
+ buf[cpp] = '\0';
+ if (USE_HASHTABLE) {
+ xpmHashAtom *slot;
+diff -urN xc/extras/Xpm~/lib/data.c xc/extras/Xpm/lib/data.c
+--- xc/extras/Xpm~/lib/data.c 2000-02-23 15:28:14.000000000 -0500
++++ xc/extras/Xpm/lib/data.c 2004-09-15 16:30:40.000000000 -0500
+@@ -374,7 +374,7 @@
+ {
+ if (!data->type)
+ *cmt = NULL;
+- else if (data->CommentLength) {
++ else if (data->CommentLength != 0 && data->CommentLength < SIZE_MAX - 1) {
+ *cmt = (char *) XpmMalloc(data->CommentLength + 1);
+ strncpy(*cmt, data->Comment, data->CommentLength);
+ (*cmt)[data->CommentLength] = '\0';
+diff -urN xc/extras/Xpm~/lib/hashtab.c xc/extras/Xpm/lib/hashtab.c
+--- xc/extras/Xpm~/lib/hashtab.c 1999-01-11 08:23:11.000000000 -0500
++++ xc/extras/Xpm/lib/hashtab.c 2004-09-15 16:30:40.000000000 -0500
+@@ -135,7 +135,7 @@
+ xpmHashTable *table;
+ {
+ xpmHashAtom *atomTable = table->atomTable;
+- int size = table->size;
++ unsigned int size = table->size;
+ xpmHashAtom *t, *p;
+ int i;
+ int oldSize = size;
+@@ -144,6 +144,8 @@
+ HASH_TABLE_GROWS
+ table->size = size;
+ table->limit = size / 3;
++ if (size >= SIZE_MAX / sizeof(*atomTable))
++ return (XpmNoMemory);
+ atomTable = (xpmHashAtom *) XpmMalloc(size * sizeof(*atomTable));
+ if (!atomTable)
+ return (XpmNoMemory);
+@@ -204,6 +206,8 @@
+ table->size = INITIAL_HASH_SIZE;
+ table->limit = table->size / 3;
+ table->used = 0;
++ if (table->size >= SIZE_MAX / sizeof(*atomTable))
++ return (XpmNoMemory);
+ atomTable = (xpmHashAtom *) XpmMalloc(table->size * sizeof(*atomTable));
+ if (!atomTable)
+ return (XpmNoMemory);
+diff -urN xc/extras/Xpm~/lib/parse.c xc/extras/Xpm/lib/parse.c
+--- xc/extras/Xpm~/lib/parse.c 2000-09-26 10:56:43.000000000 -0500
++++ xc/extras/Xpm/lib/parse.c 2004-09-16 15:31:32.000000000 -0500
+@@ -1,3 +1,4 @@
++/* $XdotOrg: pre-CVS proposed fix for CESA-2004-003 alanc 7/25/2004 $ */
+ /*
+ * Copyright (C) 1989-95 GROUPE BULL
+ *
+@@ -43,6 +44,23 @@
+ #include <ctype.h>
+ #include <string.h>
+
++#ifdef HAS_STRLCAT
++# define STRLCAT(dst, src, dstsize) { \
++ if (strlcat(dst, src, dstsize) >= (dstsize)) \
++ return (XpmFileInvalid); }
++# define STRLCPY(dst, src, dstsize) { \
++ if (strlcpy(dst, src, dstsize) >= (dstsize)) \
++ return (XpmFileInvalid); }
++#else
++# define STRLCAT(dst, src, dstsize) { \
++ if ((strlen(dst) + strlen(src)) < (dstsize)) \
++ strcat(dst, src); \
++ else return (XpmFileInvalid); }
++# define STRLCPY(dst, src, dstsize) { \
++ if (strlen(src) < (dstsize)) \
++ strcpy(dst, src); \
++ else return (XpmFileInvalid); }
++
+ LFUNC(ParsePixels, int, (xpmData *data, unsigned int width,
+ unsigned int height, unsigned int ncolors,
+ unsigned int cpp, XpmColor *colorTable,
+@@ -65,7 +83,7 @@
+ unsigned int *extensions;
+ {
+ unsigned int l;
+- char buf[BUFSIZ];
++ char buf[BUFSIZ + 1];
+
+ if (!data->format) { /* XPM 2 or 3 */
+
+@@ -174,10 +192,10 @@
+ XpmColor **colorTablePtr;
+ xpmHashTable *hashtable;
+ {
+- unsigned int key, l, a, b;
++ unsigned int key = 0, l, a, b, len;
+ unsigned int curkey; /* current color key */
+ unsigned int lastwaskey; /* key read */
+- char buf[BUFSIZ];
++ char buf[BUFSIZ+1];
+ char curbuf[BUFSIZ]; /* current buffer */
+ char **sptr, *s;
+ XpmColor *color;
+@@ -185,6 +203,8 @@
+ char **defaults;
+ int ErrorStatus;
+
++ if (ncolors >= SIZE_MAX / sizeof(XpmColor))
++ return (XpmNoMemory);
+ colorTable = (XpmColor *) XpmCalloc(ncolors, sizeof(XpmColor));
+ if (!colorTable)
+ return (XpmNoMemory);
+@@ -196,6 +216,10 @@
+ /*
+ * read pixel value
+ */
++ if (cpp >= SIZE_MAX - 1) {
++ xpmFreeColorTable(colorTable, ncolors);
++ return (XpmNoMemory);
++ }
+ color->string = (char *) XpmMalloc(cpp + 1);
+ if (!color->string) {
+ xpmFreeColorTable(colorTable, ncolors);
+@@ -233,13 +257,14 @@
+ }
+ if (!lastwaskey && key < NKEYS) { /* open new key */
+ if (curkey) { /* flush string */
+- s = (char *) XpmMalloc(strlen(curbuf) + 1);
++ len = strlen(curbuf) + 1;
++ s = (char *) XpmMalloc(len);
+ if (!s) {
+ xpmFreeColorTable(colorTable, ncolors);
+ return (XpmNoMemory);
+ }
+ defaults[curkey] = s;
+- strcpy(s, curbuf);
++ memcpy(s, curbuf, len);
+ }
+ curkey = key + 1; /* set new key */
+ *curbuf = '\0'; /* reset curbuf */
+@@ -250,9 +275,9 @@
+ return (XpmFileInvalid);
+ }
+ if (!lastwaskey)
+- strcat(curbuf, " "); /* append space */
++ STRLCAT(curbuf, " ", sizeof(curbuf)); /* append space */
+ buf[l] = '\0';
+- strcat(curbuf, buf);/* append buf */
++ STRLCAT(curbuf, buf, sizeof(curbuf));/* append buf */
+ lastwaskey = 0;
+ }
+ }
+@@ -260,12 +285,13 @@
+ xpmFreeColorTable(colorTable, ncolors);
+ return (XpmFileInvalid);
+ }
+- s = defaults[curkey] = (char *) XpmMalloc(strlen(curbuf) + 1);
++ len = strlen(curbuf) + 1;
++ s = defaults[curkey] = (char *) XpmMalloc(len);
+ if (!s) {
+ xpmFreeColorTable(colorTable, ncolors);
+ return (XpmNoMemory);
+ }
+- strcpy(s, curbuf);
++ memcpy(s, curbuf, len);
+ }
+ } else { /* XPM 1 */
+ /* get to the beginning of the first string */
+@@ -278,6 +304,10 @@
+ /*
+ * read pixel value
+ */
++ if (cpp >= SIZE_MAX - 1) {
++ xpmFreeColorTable(colorTable, ncolors);
++ return (XpmNoMemory);
++ }
+ color->string = (char *) XpmMalloc(cpp + 1);
+ if (!color->string) {
+ xpmFreeColorTable(colorTable, ncolors);
+@@ -306,16 +336,17 @@
+ *curbuf = '\0'; /* init curbuf */
+ while (l = xpmNextWord(data, buf, BUFSIZ)) {
+ if (*curbuf != '\0')
+- strcat(curbuf, " ");/* append space */
++ STRLCAT(curbuf, " ", sizeof(curbuf));/* append space */
+ buf[l] = '\0';
+- strcat(curbuf, buf); /* append buf */
++ STRLCAT(curbuf, buf, sizeof(curbuf)); /* append buf */
+ }
+- s = (char *) XpmMalloc(strlen(curbuf) + 1);
++ len = strlen(curbuf) + 1;
++ s = (char *) XpmMalloc(len);
+ if (!s) {
+ xpmFreeColorTable(colorTable, ncolors);
+ return (XpmNoMemory);
+ }
+- strcpy(s, curbuf);
++ memcpy(s, curbuf, len);
+ color->c_color = s;
+ *curbuf = '\0'; /* reset curbuf */
+ if (a < ncolors - 1)
+@@ -340,6 +371,9 @@
+ unsigned int *iptr, *iptr2;
+ unsigned int a, x, y;
+
++ if ((height > 0 && width >= SIZE_MAX / height) ||
++ width * height >= SIZE_MAX / sizeof(unsigned int))
++ return XpmNoMemory;
+ #ifndef FOR_MSW
+ iptr2 = (unsigned int *) XpmMalloc(sizeof(unsigned int) * width * height);
+ #else
+@@ -363,6 +397,9 @@
+ {
+ unsigned short colidx[256];
+
++ if (ncolors > 256)
++ return (XpmFileInvalid);
++
+ bzero((char *)colidx, 256 * sizeof(short));
+ for (a = 0; a < ncolors; a++)
+ colidx[(unsigned char)colorTable[a].string[0]] = a + 1;
+@@ -441,6 +478,9 @@
+ char *s;
+ char buf[BUFSIZ];
+
++ if (cpp >= sizeof(buf))
++ return (XpmFileInvalid);
++
+ buf[cpp] = '\0';
+ if (USE_HASHTABLE) {
+ xpmHashAtom *slot;
+diff -urN xc/extras/Xpm~/lib/scan.c xc/extras/Xpm/lib/scan.c
+--- xc/extras/Xpm~/lib/scan.c 1999-01-11 08:23:11.000000000 -0500
++++ xc/extras/Xpm/lib/scan.c 2004-09-15 16:30:40.000000000 -0500
+@@ -103,7 +103,8 @@
+ LFUNC(ScanTransparentColor, int, (XpmColor *color, unsigned int cpp,
+ XpmAttributes *attributes));
+
+-LFUNC(ScanOtherColors, int, (Display *display, XpmColor *colors, int ncolors,
++LFUNC(ScanOtherColors, int, (Display *display, XpmColor *colors,
++ unsigned int ncolors,
+ Pixel *pixels, unsigned int mask,
+ unsigned int cpp, XpmAttributes *attributes));
+
+@@ -228,11 +229,17 @@
+ else
+ cpp = 0;
+
++ if ((height > 0 && width >= SIZE_MAX / height) ||
++ width * height >= SIZE_MAX / sizeof(unsigned int))
++ RETURN(XpmNoMemory);
+ pmap.pixelindex =
+ (unsigned int *) XpmCalloc(width * height, sizeof(unsigned int));
+ if (!pmap.pixelindex)
+ RETURN(XpmNoMemory);
+
++ if (pmap.size >= SIZE_MAX / sizeof(Pixel))
++ RETURN(XpmNoMemory);
++
+ pmap.pixels = (Pixel *) XpmMalloc(sizeof(Pixel) * pmap.size);
+ if (!pmap.pixels)
+ RETURN(XpmNoMemory);
+@@ -297,7 +304,8 @@
+ * get rgb values and a string of char, and possibly a name for each
+ * color
+ */
+-
++ if (pmap.ncolors >= SIZE_MAX / sizeof(XpmColor))
++ RETURN(XpmNoMemory);
+ colorTable = (XpmColor *) XpmCalloc(pmap.ncolors, sizeof(XpmColor));
+ if (!colorTable)
+ RETURN(XpmNoMemory);
+@@ -356,6 +364,8 @@
+
+ /* first get a character string */
+ a = 0;
++ if (cpp >= SIZE_MAX - 1)
++ return (XpmNoMemory);
+ if (!(s = color->string = (char *) XpmMalloc(cpp + 1)))
+ return (XpmNoMemory);
+ *s++ = printable[c = a % MAXPRINTABLE];
+@@ -403,7 +413,7 @@
+ ScanOtherColors(display, colors, ncolors, pixels, mask, cpp, attributes)
+ Display *display;
+ XpmColor *colors;
+- int ncolors;
++ unsigned int ncolors;
+ Pixel *pixels;
+ unsigned int mask;
+ unsigned int cpp;
+@@ -447,6 +457,8 @@
+ }
+
+ /* first get character strings and rgb values */
++ if (ncolors >= SIZE_MAX / sizeof(XColor) || cpp >= SIZE_MAX - 1)
++ return (XpmNoMemory);
+ xcolors = (XColor *) XpmMalloc(sizeof(XColor) * ncolors);
+ if (!xcolors)
+ return (XpmNoMemory);
+diff -urN xc/lib/Xpm~/Imakefile xc/lib/Xpm/Imakefile
+--- xc/lib/Xpm~/Imakefile 2000-09-19 07:46:06.000000000 -0500
++++ xc/lib/Xpm/Imakefile 2004-09-15 16:30:40.000000000 -0500
+@@ -42,11 +42,16 @@
+ SPRINTFDEF = -DVOID_SPRINTF
+ #endif
+
++#if HasStrlcat
++STRLCATDEF = -DHAS_STRLCAT
++#endif
++
+ #if defined(Win32Architecture)
+ ZPIPEDEF = -DNO_ZPIPE
+ #endif
+
+-DEFINES = $(STRDUPDEF) $(STRCASECMPDEF) $(SPRINTFDEF) $(ZPIPEDEF) $(ZFILEDEF)
++DEFINES = $(STRDUPDEF) $(STRCASECMPDEF) $(SPRINTFDEF) $(STRLCATDEF) \
++ $(ZPIPEDEF) $(ZFILEDEF)
+
+ HEADERS = xpm.h
+
Property changes on: branches/4.1.0/woody/debian/patches/076_SECURITY_libXpm_vulnerabilities.diff
___________________________________________________________________
Name: svn:keywords
+ Id
Reply to: