Submitted By: Ken Moffat <ken at linuxfromscratch dot org>
Date: 2010-12-18
Initial Package Version: 9.00
Upstream Status: unknown
Origin: debian and fedora
Description: First part is a fix for CVE-2009-4270 (supposedly fixed in 8.71)
found at debian, remainder are two fixes for null dereferences in the included
jbig2dec (note that ghostscript _is_ the upstream for jbig2dec).

diff -Naur ghostscript-9.00.orig//base/gsmisc.c ghostscript-9.00/base/gsmisc.c
--- ghostscript-9.00.orig//base/gsmisc.c	2010-05-06 17:04:27.000000000 +0100
+++ ghostscript-9.00/base/gsmisc.c	2010-12-18 18:07:08.000000000 +0000
@@ -260,7 +260,7 @@
     va_list ap;
 
     va_start(ap, fmt);
-    vsprintf(msg, fmt, ap);
+    vsnprintf(msg, sizeof(msg), fmt, ap);
     msg[sizeof(msg) - 1] = 0;
     va_end(ap);
 
diff -Naur ghostscript-9.00.orig//jbig2dec/jbig2_image.c ghostscript-9.00/jbig2dec/jbig2_image.c
--- ghostscript-9.00.orig//jbig2dec/jbig2_image.c	2010-07-20 09:23:05.000000000 +0100
+++ ghostscript-9.00/jbig2dec/jbig2_image.c	2010-12-18 18:08:55.000000000 +0000
@@ -60,15 +60,18 @@
 /* clone an image pointer by bumping its reference count */
 Jbig2Image* jbig2_image_clone(Jbig2Ctx *ctx, Jbig2Image *image)
 {
-	image->refcount++;
+	if (image)
+		image->refcount++;
 	return image;
 }
 
 /* release an image pointer, freeing it it appropriate */
 void jbig2_image_release(Jbig2Ctx *ctx, Jbig2Image *image)
 {
-	image->refcount--;
-	if (!image->refcount) jbig2_image_free(ctx, image);
+	if (image) {
+		image->refcount--;
+		if (!image->refcount) jbig2_image_free(ctx, image);
+	}
 }
 
 /* free a Jbig2Image structure and its associated memory */
diff -Naur ghostscript-9.00.orig//jbig2dec/jbig2_symbol_dict.c ghostscript-9.00/jbig2dec/jbig2_symbol_dict.c
--- ghostscript-9.00.orig//jbig2dec/jbig2_symbol_dict.c	2010-07-20 10:46:34.000000000 +0100
+++ ghostscript-9.00/jbig2dec/jbig2_symbol_dict.c	2010-12-18 18:07:37.000000000 +0000
@@ -364,6 +364,11 @@
 		  memcpy(region_params.gbat, params->sdat, sdat_bytes);
 
 		  image = jbig2_image_new(ctx, SYMWIDTH, HCHEIGHT);
+		  if (image == NULL) {
+		      jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+				  "failed to allocate image storage");
+		      return NULL;
+		  }
 
 		  code = jbig2_decode_generic_region(ctx, segment, &region_params,
 						     as, image, GB_stats);
@@ -514,6 +519,11 @@
 			ID, RDX, RDY);
 
 		      image = jbig2_image_new(ctx, SYMWIDTH, HCHEIGHT);
+		      if (image == NULL) {
+			  jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+				      "failed to allocate image storage");
+			  return NULL;
+		      }
 
 		      /* Table 18 */
 		      rparams.GRTEMPLATE = params->SDRTEMPLATE;
@@ -632,6 +642,16 @@
 	for (j = HCFIRSTSYM; j < NSYMSDECODED; j++) {
 	  Jbig2Image *glyph;
 	  glyph = jbig2_image_new(ctx, SDNEWSYMWIDTHS[j], HCHEIGHT);
+	  if (glyph == NULL) {
+	      jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			  "error allocating image storage for glyph");
+	      while (--j >= HCFIRSTSYM) {
+		  jbig2_image_release(ctx, SDNEWSYMS->glyphs[j]);
+		  SDNEWSYMS->glyphs[j] = NULL;
+	      }
+	      jbig2_image_release(ctx, image);
+	      return NULL;
+	  }
 	  jbig2_image_compose(ctx, glyph, image,
 		-x, 0, JBIG2_COMPOSE_REPLACE);
 	  x += SDNEWSYMWIDTHS[j];
diff -Naur ghostscript-9.00.orig//jbig2dec/jbig2_symbol_dict.c.orig ghostscript-9.00/jbig2dec/jbig2_symbol_dict.c.orig
--- ghostscript-9.00.orig//jbig2dec/jbig2_symbol_dict.c.orig	1970-01-01 01:00:00.000000000 +0100
+++ ghostscript-9.00/jbig2dec/jbig2_symbol_dict.c.orig	2010-07-20 10:46:34.000000000 +0100
@@ -0,0 +1,959 @@
+/*
+    jbig2dec
+
+    Copyright (C) 2001-2005 Artifex Software, Inc.
+
+    This software is distributed under license and may not
+    be copied, modified or distributed except as expressly
+    authorized under the terms of the license contained in
+    the file LICENSE in this distribution.
+
+    For further licensing information refer to http://artifex.com/ or
+    contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+    San Rafael, CA  94903, U.S.A., +1(415)492-9861.
+*/
+
+/* symbol dictionary segment decode and support */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "os_types.h"
+
+#include <stddef.h>
+#include <string.h> /* memset() */
+
+#include "jbig2.h"
+#include "jbig2_priv.h"
+#include "jbig2_arith.h"
+#include "jbig2_arith_int.h"
+#include "jbig2_arith_iaid.h"
+#include "jbig2_huffman.h"
+#include "jbig2_generic.h"
+#include "jbig2_mmr.h"
+#include "jbig2_symbol_dict.h"
+#include "jbig2_text.h"
+
+#if defined(OUTPUT_PBM) || defined(DUMP_SYMDICT)
+#include <stdio.h>
+#include "jbig2_image.h"
+#endif
+
+/* Table 13 */
+typedef struct {
+  bool SDHUFF;
+  bool SDREFAGG;
+  int32_t SDNUMINSYMS;
+  Jbig2SymbolDict *SDINSYMS;
+  uint32_t SDNUMNEWSYMS;
+  uint32_t SDNUMEXSYMS;
+  Jbig2HuffmanTable *SDHUFFDH;
+  Jbig2HuffmanTable *SDHUFFDW;
+  Jbig2HuffmanTable *SDHUFFBMSIZE;
+  Jbig2HuffmanTable *SDHUFFAGGINST;
+  int SDTEMPLATE;
+  int8_t sdat[8];
+  bool SDRTEMPLATE;
+  int8_t sdrat[4];
+} Jbig2SymbolDictParams;
+
+
+/* Utility routines */
+
+#ifdef DUMP_SYMDICT
+void
+jbig2_dump_symbol_dict(Jbig2Ctx *ctx, Jbig2Segment *segment)
+{
+    Jbig2SymbolDict *dict = (Jbig2SymbolDict *)segment->result;
+    int index;
+    char filename[24];
+
+    if (dict == NULL) return;
+    jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
+        "dumping symbol dict as %d individual png files\n", dict->n_symbols);
+    for (index = 0; index < dict->n_symbols; index++) {
+        snprintf(filename, sizeof(filename), "symbol_%02d-%04d.png",
+		segment->number, index);
+	jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+	  "dumping symbol %d/%d as '%s'", index, dict->n_symbols, filename);
+#ifdef HAVE_LIBPNG
+        jbig2_image_write_png_file(dict->glyphs[index], filename);
+#else
+        jbig2_image_write_pbm_file(dict->glyphs[index], filename);
+#endif
+    }
+}
+#endif /* DUMP_SYMDICT */
+
+/* return a new empty symbol dict */
+Jbig2SymbolDict *
+jbig2_sd_new(Jbig2Ctx *ctx, int n_symbols)
+{
+   Jbig2SymbolDict *new = NULL;
+
+   new = jbig2_new(ctx, Jbig2SymbolDict, 1);
+   if (new != NULL) {
+     new->glyphs = jbig2_new(ctx, Jbig2Image*, n_symbols);
+     new->n_symbols = n_symbols;
+   } else {
+     return NULL;
+   }
+
+   if (new->glyphs != NULL) {
+     memset(new->glyphs, 0, n_symbols*sizeof(Jbig2Image*));
+   } else {
+     jbig2_free(ctx->allocator, new);
+     return NULL;
+   }
+
+   return new;
+}
+
+/* release the memory associated with a symbol dict */
+void
+jbig2_sd_release(Jbig2Ctx *ctx, Jbig2SymbolDict *dict)
+{
+   int i;
+
+   if (dict == NULL) return;
+   for (i = 0; i < dict->n_symbols; i++)
+     if (dict->glyphs[i]) jbig2_image_release(ctx, dict->glyphs[i]);
+   jbig2_free(ctx->allocator, dict->glyphs);
+   jbig2_free(ctx->allocator, dict);
+}
+
+/* get a particular glyph by index */
+Jbig2Image *
+jbig2_sd_glyph(Jbig2SymbolDict *dict, unsigned int id)
+{
+   if (dict == NULL) return NULL;
+   return dict->glyphs[id];
+}
+
+/* count the number of dictionary segments referred to by the given segment */
+int
+jbig2_sd_count_referred(Jbig2Ctx *ctx, Jbig2Segment *segment)
+{
+    int index;
+    Jbig2Segment *rsegment;
+    int n_dicts = 0;
+
+    for (index = 0; index < segment->referred_to_segment_count; index++) {
+        rsegment = jbig2_find_segment(ctx, segment->referred_to_segments[index]);
+        if (rsegment && ((rsegment->flags & 63) == 0)) n_dicts++;
+    }
+
+    return (n_dicts);
+}
+
+/* return an array of pointers to symbol dictionaries referred to by the given segment */
+Jbig2SymbolDict **
+jbig2_sd_list_referred(Jbig2Ctx *ctx, Jbig2Segment *segment)
+{
+    int index;
+    Jbig2Segment *rsegment;
+    Jbig2SymbolDict **dicts;
+    int n_dicts = jbig2_sd_count_referred(ctx, segment);
+    int dindex = 0;
+
+    dicts = jbig2_new(ctx, Jbig2SymbolDict*, n_dicts);
+    for (index = 0; index < segment->referred_to_segment_count; index++) {
+        rsegment = jbig2_find_segment(ctx, segment->referred_to_segments[index]);
+        if (rsegment && ((rsegment->flags & 63) == 0)) {
+            /* add this referred to symbol dictionary */
+            dicts[dindex++] = (Jbig2SymbolDict *)rsegment->result;
+        }
+    }
+
+    if (dindex != n_dicts) {
+        /* should never happen */
+        jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+            "counted %d symbol dictionaries but build a list with %d.\n",
+            n_dicts, dindex);
+    }
+
+    return (dicts);
+}
+
+/* generate a new symbol dictionary by concatenating a list of
+   existing dictionaries */
+Jbig2SymbolDict *
+jbig2_sd_cat(Jbig2Ctx *ctx, int n_dicts, Jbig2SymbolDict **dicts)
+{
+  int i,j,k, symbols;
+  Jbig2SymbolDict *new = NULL;
+
+  /* count the imported symbols and allocate a new array */
+  symbols = 0;
+  for(i = 0; i < n_dicts; i++)
+    symbols += dicts[i]->n_symbols;
+
+  /* fill a new array with cloned glyph pointers */
+  new = jbig2_sd_new(ctx, symbols);
+  if (new != NULL) {
+    k = 0;
+    for (i = 0; i < n_dicts; i++)
+      for (j = 0; j < dicts[i]->n_symbols; j++)
+        new->glyphs[k++] = jbig2_image_clone(ctx, dicts[i]->glyphs[j]);
+  }
+
+  return new;
+}
+
+
+/* Decoding routines */
+
+/* 6.5 */
+static Jbig2SymbolDict *
+jbig2_decode_symbol_dict(Jbig2Ctx *ctx,
+			 Jbig2Segment *segment,
+			 const Jbig2SymbolDictParams *params,
+			 const byte *data, size_t size,
+			 Jbig2ArithCx *GB_stats,
+			 Jbig2ArithCx *GR_stats)
+{
+  Jbig2SymbolDict *SDNEWSYMS;
+  Jbig2SymbolDict *SDEXSYMS;
+  int32_t HCHEIGHT;
+  uint32_t NSYMSDECODED;
+  int32_t SYMWIDTH, TOTWIDTH;
+  uint32_t HCFIRSTSYM;
+  uint32_t *SDNEWSYMWIDTHS = NULL;
+  int SBSYMCODELEN = 0;
+  Jbig2WordStream *ws = NULL;
+  Jbig2HuffmanState *hs = NULL;
+  Jbig2HuffmanTable *SDHUFFRDX = NULL;
+  Jbig2ArithState *as = NULL;
+  Jbig2ArithIntCtx *IADH = NULL;
+  Jbig2ArithIntCtx *IADW = NULL;
+  Jbig2ArithIntCtx *IAEX = NULL;
+  Jbig2ArithIntCtx *IAAI = NULL;
+  Jbig2ArithIaidCtx *IAID = NULL;
+  Jbig2ArithIntCtx *IARDX = NULL;
+  Jbig2ArithIntCtx *IARDY = NULL;
+  int code = 0;
+  Jbig2SymbolDict **refagg_dicts;
+  int n_refagg_dicts = 1;
+
+  Jbig2TextRegionParams *tparams = NULL;
+
+  /* 6.5.5 (3) */
+  HCHEIGHT = 0;
+  NSYMSDECODED = 0;
+
+  ws = jbig2_word_stream_buf_new(ctx, data, size);
+
+  if (!params->SDHUFF) {
+      as = jbig2_arith_new(ctx, ws);
+      IADH = jbig2_arith_int_ctx_new(ctx);
+      IADW = jbig2_arith_int_ctx_new(ctx);
+      IAEX = jbig2_arith_int_ctx_new(ctx);
+      IAAI = jbig2_arith_int_ctx_new(ctx);
+      if (params->SDREFAGG) {
+	  int tmp = params->SDINSYMS->n_symbols + params->SDNUMNEWSYMS;
+	  for (SBSYMCODELEN = 0; (1 << SBSYMCODELEN) < tmp; SBSYMCODELEN++);
+	  IAID = jbig2_arith_iaid_ctx_new(ctx, SBSYMCODELEN);
+	  IARDX = jbig2_arith_int_ctx_new(ctx);
+	  IARDY = jbig2_arith_int_ctx_new(ctx);
+      }
+  } else {
+      jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+	"huffman coded symbol dictionary");
+      hs = jbig2_huffman_new(ctx, ws);
+      SDHUFFRDX = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_O);
+      if (!params->SDREFAGG) {
+	  SDNEWSYMWIDTHS = jbig2_new(ctx, uint32_t, params->SDNUMNEWSYMS);
+	  if (SDNEWSYMWIDTHS == NULL) {
+	    jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+		"could not allocate storage for symbol widths");
+	    return NULL;
+	  }
+      }
+  }
+
+  SDNEWSYMS = jbig2_sd_new(ctx, params->SDNUMNEWSYMS);
+
+  /* 6.5.5 (4a) */
+  while (NSYMSDECODED < params->SDNUMNEWSYMS) {
+      int32_t HCDH, DW;
+
+      /* 6.5.6 */
+      if (params->SDHUFF) {
+	  HCDH = jbig2_huffman_get(hs, params->SDHUFFDH, &code);
+      } else {
+	  code = jbig2_arith_int_decode(IADH, as, &HCDH);
+      }
+
+      if (code != 0) {
+	jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+	  "error or OOB decoding height class delta (%d)\n", code);
+      }
+
+      /* 6.5.5 (4b) */
+      HCHEIGHT = HCHEIGHT + HCDH;
+      SYMWIDTH = 0;
+      TOTWIDTH = 0;
+      HCFIRSTSYM = NSYMSDECODED;
+
+      if (HCHEIGHT < 0) {
+	  /* todo: mem cleanup */
+	  code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			   "Invalid HCHEIGHT value");
+          return NULL;
+      }
+#ifdef JBIG2_DEBUG
+      jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+        "HCHEIGHT = %d", HCHEIGHT);
+#endif
+      jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+        "decoding height class %d with %d syms decoded", HCHEIGHT, NSYMSDECODED);
+
+      for (;;) {
+	  /* check for broken symbol table */
+ 	  if (NSYMSDECODED > params->SDNUMNEWSYMS)
+	    {
+	      /* todo: mem cleanup? */
+	      jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+	        "No OOB signalling end of height class %d", HCHEIGHT);
+	      break;
+	    }
+	  /* 6.5.7 */
+	  if (params->SDHUFF) {
+	      DW = jbig2_huffman_get(hs, params->SDHUFFDW, &code);
+	  } else {
+	      code = jbig2_arith_int_decode(IADW, as, &DW);
+	  }
+
+	  /* 6.5.5 (4c.i) */
+	  if (code == 1) {
+	    jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+	    " OOB signals end of height class %d", HCHEIGHT);
+	    break;
+	  }
+	  SYMWIDTH = SYMWIDTH + DW;
+	  TOTWIDTH = TOTWIDTH + SYMWIDTH;
+	  if (SYMWIDTH < 0) {
+	      /* todo: mem cleanup */
+              code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+                "Invalid SYMWIDTH value (%d) at symbol %d", SYMWIDTH, NSYMSDECODED+1);
+              return NULL;
+          }
+#ifdef JBIG2_DEBUG
+	  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+            "SYMWIDTH = %d TOTWIDTH = %d", SYMWIDTH, TOTWIDTH);
+#endif
+	  /* 6.5.5 (4c.ii) */
+	  if (!params->SDHUFF || params->SDREFAGG) {
+#ifdef JBIG2_DEBUG
+		jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+		  "SDHUFF = %d; SDREFAGG = %d", params->SDHUFF, params->SDREFAGG);
+#endif
+	      /* 6.5.8 */
+	      if (!params->SDREFAGG) {
+		  Jbig2GenericRegionParams region_params;
+		  int sdat_bytes;
+		  Jbig2Image *image;
+
+		  /* Table 16 */
+		  region_params.MMR = 0;
+		  region_params.GBTEMPLATE = params->SDTEMPLATE;
+		  region_params.TPGDON = 0;
+		  region_params.USESKIP = 0;
+		  sdat_bytes = params->SDTEMPLATE == 0 ? 8 : 2;
+		  memcpy(region_params.gbat, params->sdat, sdat_bytes);
+
+		  image = jbig2_image_new(ctx, SYMWIDTH, HCHEIGHT);
+
+		  code = jbig2_decode_generic_region(ctx, segment, &region_params,
+						     as, image, GB_stats);
+		  /* todo: handle errors */
+
+                  SDNEWSYMS->glyphs[NSYMSDECODED] = image;
+
+	      } else {
+                  /* 6.5.8.2 refinement/aggregate symbol */
+                  uint32_t REFAGGNINST;
+
+		  if (params->SDHUFF) {
+		      REFAGGNINST = jbig2_huffman_get(hs, params->SDHUFFAGGINST, &code);
+		  } else {
+		      code = jbig2_arith_int_decode(IAAI, as, (int32_t*)&REFAGGNINST);
+		  }
+		  if (code || (int32_t)REFAGGNINST <= 0) {
+		      code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			"invalid number of symbols or OOB in aggregate glyph");
+		      return NULL;
+		  }
+
+		  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+		    "aggregate symbol coding (%d instances)", REFAGGNINST);
+
+		  if (REFAGGNINST > 1) {
+		      Jbig2Image *image;
+		      int i;
+
+		      if (tparams == NULL)
+		      {
+			  /* First time through, we need to initialise the */
+			  /* various tables for Huffman or adaptive encoding */
+			  /* as well as the text region parameters structure */
+			  refagg_dicts = jbig2_new(ctx, Jbig2SymbolDict*, n_refagg_dicts);
+		          if (refagg_dicts == NULL) {
+			      code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			       "Out of memory allocating dictionary array");
+		              return NULL;
+		          }
+		          refagg_dicts[0] = jbig2_sd_new(ctx, params->SDNUMINSYMS + params->SDNUMNEWSYMS);
+		          if (refagg_dicts[0] == NULL) {
+			      code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			       "Out of memory allocating symbol dictionary");
+		              jbig2_free(ctx->allocator, refagg_dicts);
+		              return NULL;
+		          }
+		          refagg_dicts[0]->n_symbols = params->SDNUMINSYMS + params->SDNUMNEWSYMS;
+		          for (i=0;i < params->SDNUMINSYMS;i++)
+		          {
+			      refagg_dicts[0]->glyphs[i] = jbig2_image_clone(ctx, params->SDINSYMS->glyphs[i]);
+		          }
+
+			  tparams = jbig2_new(ctx, Jbig2TextRegionParams, 1);
+			  if (tparams == NULL) {
+			      code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			      "Out of memory creating text region params");
+			      jbig2_sd_release(ctx, refagg_dicts[0]);
+			      jbig2_free(ctx->allocator, refagg_dicts);
+			      return NULL;
+			  }
+    		          if (!params->SDHUFF) {
+			      /* Values from Table 17, section 6.5.8.2 (2) */
+			      tparams->IADT = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IAFS = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IADS = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IAIT = jbig2_arith_int_ctx_new(ctx);
+			      /* Table 31 */
+			      for (SBSYMCODELEN = 0; (1 << SBSYMCODELEN) <
+				  (int)(params->SDNUMINSYMS + params->SDNUMNEWSYMS); SBSYMCODELEN++);
+			      tparams->IAID = jbig2_arith_iaid_ctx_new(ctx, SBSYMCODELEN);
+			      tparams->IARI = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IARDW = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IARDH = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IARDX = jbig2_arith_int_ctx_new(ctx);
+			      tparams->IARDY = jbig2_arith_int_ctx_new(ctx);
+			  } else {
+			      tparams->SBHUFFFS = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_F);   /* Table B.6 */
+			      tparams->SBHUFFDS = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_H);  /* Table B.8 */
+			      tparams->SBHUFFDT = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_K);  /* Table B.11 */
+			      tparams->SBHUFFRDW = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_O); /* Table B.15 */
+			      tparams->SBHUFFRDH = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_O); /* Table B.15 */
+			      tparams->SBHUFFRDX = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_O); /* Table B.15 */
+			      tparams->SBHUFFRDY = jbig2_build_huffman_table(ctx,
+				&jbig2_huffman_params_O); /* Table B.15 */
+			  }
+			  tparams->SBHUFF = params->SDHUFF;
+			  tparams->SBREFINE = 1;
+			  tparams->SBSTRIPS = 1;
+			  tparams->SBDEFPIXEL = 0;
+			  tparams->SBCOMBOP = JBIG2_COMPOSE_OR;
+			  tparams->TRANSPOSED = 0;
+			  tparams->REFCORNER = JBIG2_CORNER_TOPLEFT;
+			  tparams->SBDSOFFSET = 0;
+			  tparams->SBRTEMPLATE = params->SDRTEMPLATE;
+		      }
+		      tparams->SBNUMINSTANCES = REFAGGNINST;
+
+      		      image = jbig2_image_new(ctx, SYMWIDTH, HCHEIGHT);
+		      if (image == NULL) {
+			  code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			   "Out of memory creating symbol image");
+		          jbig2_free(ctx->allocator, tparams);
+			  jbig2_sd_release(ctx, refagg_dicts[0]);
+		          jbig2_free(ctx->allocator, refagg_dicts);
+		          return NULL;
+		      }
+
+		      /* multiple symbols are handled as a text region */
+		      jbig2_decode_text_region(ctx, segment, tparams, (const Jbig2SymbolDict * const *)refagg_dicts,
+			  n_refagg_dicts, image, data, size, GR_stats, as, (Jbig2WordStream *)NULL);
+
+		      SDNEWSYMS->glyphs[NSYMSDECODED] = image;
+		      refagg_dicts[0]->glyphs[params->SDNUMINSYMS + NSYMSDECODED] = jbig2_image_clone(ctx, SDNEWSYMS->glyphs[NSYMSDECODED]);
+		  } else {
+		      /* 6.5.8.2.2 */
+		      /* bool SBHUFF = params->SDHUFF; */
+		      Jbig2RefinementRegionParams rparams;
+		      Jbig2Image *image;
+		      uint32_t ID;
+		      int32_t RDX, RDY;
+		      int ninsyms = params->SDINSYMS->n_symbols;
+
+		      if (params->SDHUFF) {
+			  ID = jbig2_huffman_get_bits(hs, SBSYMCODELEN);
+			  RDX = jbig2_huffman_get(hs, SDHUFFRDX, &code);
+			  RDY = jbig2_huffman_get(hs, SDHUFFRDX, &code);
+		      } else {
+			  code = jbig2_arith_iaid_decode(IAID, as, (int32_t*)&ID);
+		          code = jbig2_arith_int_decode(IARDX, as, &RDX);
+		          code = jbig2_arith_int_decode(IARDY, as, &RDY);
+		      }
+
+		      if (ID >= ninsyms+NSYMSDECODED) {
+			code = jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+			  "refinement references unknown symbol %d", ID);
+			return NULL;
+		      }
+
+		      jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+			"symbol is a refinement of id %d with the refinement applied at (%d,%d)",
+			ID, RDX, RDY);
+
+		      image = jbig2_image_new(ctx, SYMWIDTH, HCHEIGHT);
+
+		      /* Table 18 */
+		      rparams.GRTEMPLATE = params->SDRTEMPLATE;
+		      rparams.reference = (ID < ninsyms) ?
+					params->SDINSYMS->glyphs[ID] :
+					SDNEWSYMS->glyphs[ID-ninsyms];
+		      rparams.DX = RDX;
+		      rparams.DY = RDY;
+		      rparams.TPGRON = 0;
+		      memcpy(rparams.grat, params->sdrat, 4);
+		      jbig2_decode_refinement_region(ctx, segment,
+		          &rparams, as, image, GR_stats);
+
+		      SDNEWSYMS->glyphs[NSYMSDECODED] = image;
+
+		  }
+               }
+
+#ifdef OUTPUT_PBM
+		  {
+		    char name[64];
+		    FILE *out;
+		    snprintf(name, 64, "sd.%04d.%04d.pbm",
+		             segment->number, NSYMSDECODED);
+		    out = fopen(name, "wb");
+                    jbig2_image_write_pbm(SDNEWSYMS->glyphs[NSYMSDECODED], out);
+		    jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+			"writing out glyph as '%s' ...", name);
+		    fclose(out);
+		  }
+#endif
+
+	  }
+
+	  /* 6.5.5 (4c.iii) */
+	  if (params->SDHUFF && !params->SDREFAGG) {
+	    SDNEWSYMWIDTHS[NSYMSDECODED] = SYMWIDTH;
+	  }
+
+	  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+            "decoded symbol %d of %d (%dx%d)",
+		NSYMSDECODED, params->SDNUMNEWSYMS,
+		SYMWIDTH, HCHEIGHT);
+
+	  /* 6.5.5 (4c.iv) */
+	  NSYMSDECODED = NSYMSDECODED + 1;
+
+      } /* end height class decode loop */
+
+      /* 6.5.5 (4d) */
+      if (params->SDHUFF && !params->SDREFAGG) {
+	/* 6.5.9 */
+	Jbig2Image *image;
+	int BMSIZE = jbig2_huffman_get(hs, params->SDHUFFBMSIZE, &code);
+	int j, x;
+
+	if (code || (BMSIZE < 0)) {
+	  jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+	    "error decoding size of collective bitmap!");
+	  /* todo: memory cleanup */
+	  return NULL;
+	}
+
+	/* skip any bits before the next byte boundary */
+	jbig2_huffman_skip(hs);
+
+	image = jbig2_image_new(ctx, TOTWIDTH, HCHEIGHT);
+	if (image == NULL) {
+	  jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+	    "could not allocate collective bitmap image!");
+	  /* todo: memory cleanup */
+	  return NULL;
+	}
+
+	if (BMSIZE == 0) {
+	  /* if BMSIZE == 0 bitmap is uncompressed */
+	  const byte *src = data + jbig2_huffman_offset(hs);
+	  const int stride = (image->width >> 3) +
+		((image->width & 7) ? 1 : 0);
+	  byte *dst = image->data;
+
+	  BMSIZE = image->height * stride;
+	  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+	    "reading %dx%d uncompressed bitmap"
+	    " for %d symbols (%d bytes)",
+	    image->width, image->height, NSYMSDECODED - HCFIRSTSYM, BMSIZE);
+
+	  for (j = 0; j < image->height; j++) {
+	    memcpy(dst, src, stride);
+	    dst += image->stride;
+	    src += stride;
+	  }
+	} else {
+	  Jbig2GenericRegionParams rparams;
+
+	  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
+	    "reading %dx%d collective bitmap for %d symbols (%d bytes)",
+	    image->width, image->height, NSYMSDECODED - HCFIRSTSYM, BMSIZE);
+
+	  rparams.MMR = 1;
+	  code = jbig2_decode_generic_mmr(ctx, segment, &rparams,
+	    data + jbig2_huffman_offset(hs), BMSIZE, image);
+	  if (code) {
+	    jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+	      "error decoding MMR bitmap image!");
+	    /* todo: memory cleanup */
+	    return NULL;
+	  }
+	}
+
+	/* advance past the data we've just read */
+	jbig2_huffman_advance(hs, BMSIZE);
+
+	/* copy the collective bitmap into the symbol dictionary */
+	x = 0;
+	for (j = HCFIRSTSYM; j < NSYMSDECODED; j++) {
+	  Jbig2Image *glyph;
+	  glyph = jbig2_image_new(ctx, SDNEWSYMWIDTHS[j], HCHEIGHT);
+	  jbig2_image_compose(ctx, glyph, image,
+		-x, 0, JBIG2_COMPOSE_REPLACE);
+	  x += SDNEWSYMWIDTHS[j];
+	  SDNEWSYMS->glyphs[j] = glyph;
+	}
+	jbig2_image_release(ctx, image);
+      }
+
+  } /* end of symbol decode loop */
+
+  if (tparams != NULL)
+  {
+      if (!params->SDHUFF)
+      {
+          jbig2_arith_int_ctx_free(ctx, tparams->IADT);
+          jbig2_arith_int_ctx_free(ctx, tparams->IAFS);
+          jbig2_arith_int_ctx_free(ctx, tparams->IADS);
+          jbig2_arith_int_ctx_free(ctx, tparams->IAIT);
+          jbig2_arith_iaid_ctx_free(ctx, tparams->IAID);
+          jbig2_arith_int_ctx_free(ctx, tparams->IARI);
+          jbig2_arith_int_ctx_free(ctx, tparams->IARDW);
+          jbig2_arith_int_ctx_free(ctx, tparams->IARDH);
+          jbig2_arith_int_ctx_free(ctx, tparams->IARDX);
+          jbig2_arith_int_ctx_free(ctx, tparams->IARDY);
+      }
+      else
+      {
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFFS);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFDS);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFDT);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFRDX);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFRDY);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFRDW);
+          jbig2_release_huffman_table(ctx, tparams->SBHUFFRDH);
+      }
+      jbig2_free(ctx->allocator, tparams);
+      tparams = NULL;
+      jbig2_sd_release(ctx, refagg_dicts[0]);
+      jbig2_free(ctx->allocator, refagg_dicts);
+  }
+
+  jbig2_free(ctx->allocator, GB_stats);
+
+  /* 6.5.10 */
+  SDEXSYMS = jbig2_sd_new(ctx, params->SDNUMEXSYMS);
+  {
+    int i = 0;
+    int j = 0;
+    int k, m, exflag = 0;
+    int32_t exrunlength;
+
+    if (params->SDINSYMS != NULL)
+      m = params->SDINSYMS->n_symbols;
+    else
+      m = 0;
+    while (j < params->SDNUMEXSYMS) {
+      if (params->SDHUFF)
+      	/* FIXME: implement reading from huff table B.1 */
+        exrunlength = exflag ? params->SDNUMEXSYMS : 0;
+      else
+        code = jbig2_arith_int_decode(IAEX, as, &exrunlength);
+      if (exflag && exrunlength > params->SDNUMEXSYMS - j) {
+        jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+          "runlength too large in export symbol table (%d > %d - %d)\n",
+          exrunlength, params->SDNUMEXSYMS, j);
+        jbig2_sd_release(ctx, SDEXSYMS);
+        /* skip to the cleanup code and return SDEXSYMS = NULL */
+        SDEXSYMS = NULL;
+        break;
+      }
+      for(k = 0; k < exrunlength; k++) {
+        if (exflag) {
+          SDEXSYMS->glyphs[j++] = (i < m) ?
+            jbig2_image_clone(ctx, params->SDINSYMS->glyphs[i]) :
+            jbig2_image_clone(ctx, SDNEWSYMS->glyphs[i-m]);
+        }
+        i++;
+      }
+      exflag = !exflag;
+    }
+  }
+
+  jbig2_sd_release(ctx, SDNEWSYMS);
+
+  if (!params->SDHUFF) {
+      jbig2_arith_int_ctx_free(ctx, IADH);
+      jbig2_arith_int_ctx_free(ctx, IADW);
+      jbig2_arith_int_ctx_free(ctx, IAEX);
+      jbig2_arith_int_ctx_free(ctx, IAAI);
+      if (params->SDREFAGG) {
+        jbig2_arith_iaid_ctx_free(ctx, IAID);
+        jbig2_arith_int_ctx_free(ctx, IARDX);
+        jbig2_arith_int_ctx_free(ctx, IARDY);
+      }
+      jbig2_free(ctx->allocator, as);
+  } else {
+      if (params->SDREFAGG) {
+	jbig2_free(ctx->allocator, SDNEWSYMWIDTHS);
+      }
+      jbig2_release_huffman_table(ctx, SDHUFFRDX);
+      jbig2_free(ctx->allocator, hs);
+  }
+
+  jbig2_word_stream_buf_free(ctx, ws);
+
+  return SDEXSYMS;
+}
+
+/* 7.4.2 */
+int
+jbig2_symbol_dictionary(Jbig2Ctx *ctx, Jbig2Segment *segment,
+			const byte *segment_data)
+{
+  Jbig2SymbolDictParams params;
+  uint16_t flags;
+  int sdat_bytes;
+  int offset;
+  Jbig2ArithCx *GB_stats = NULL;
+  Jbig2ArithCx *GR_stats = NULL;
+  int table_index = 0;
+  const Jbig2HuffmanParams *huffman_params;
+
+  if (segment->data_length < 10)
+    goto too_short;
+
+  /* 7.4.2.1.1 */
+  flags = jbig2_get_int16(segment_data);
+  params.SDHUFF = flags & 1;
+  params.SDREFAGG = (flags >> 1) & 1;
+  params.SDTEMPLATE = (flags >> 10) & 3;
+  params.SDRTEMPLATE = (flags >> 12) & 1;
+
+  params.SDHUFFDH = NULL;
+  params.SDHUFFDW = NULL;
+  params.SDHUFFBMSIZE = NULL;
+  params.SDHUFFAGGINST = NULL;
+
+  if (params.SDHUFF) {
+    switch ((flags & 0x000c) >> 2) {
+      case 0: /* Table B.4 */
+	params.SDHUFFDH = jbig2_build_huffman_table(ctx,
+		                       &jbig2_huffman_params_D);
+	break;
+      case 1: /* Table B.5 */
+	params.SDHUFFDH = jbig2_build_huffman_table(ctx,
+		                       &jbig2_huffman_params_E);
+	break;
+      case 3: /* Custom table from referred segment */
+        huffman_params = jbig2_find_table(ctx, segment, table_index);
+        if (huffman_params == NULL) {
+            return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+                "Custom DH huffman table not found (%d)", table_index);
+        }
+        params.SDHUFFDH = jbig2_build_huffman_table(ctx, huffman_params);
+        ++table_index;
+        break;
+        /* FIXME: this function leaks memory when error happens.
+           i.e. not calling jbig2_release_huffman_table() */
+      case 2:
+      default:
+	return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+	    "symbol dictionary specified invalid huffman table");
+	break;
+    }
+    switch ((flags & 0x0030) >> 4) {
+      case 0: /* Table B.2 */
+	params.SDHUFFDW = jbig2_build_huffman_table(ctx,
+		                       &jbig2_huffman_params_B);
+	break;
+      case 1: /* Table B.3 */
+	params.SDHUFFDW = jbig2_build_huffman_table(ctx,
+		                       &jbig2_huffman_params_C);
+	break;
+      case 3: /* Custom table from referred segment */
+        huffman_params = jbig2_find_table(ctx, segment, table_index);
+        if (huffman_params == NULL) {
+            return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+                "Custom DW huffman table not found (%d)", table_index);
+        }
+        params.SDHUFFDW = jbig2_build_huffman_table(ctx, huffman_params);
+        ++table_index;
+        break;
+      case 2:
+      default:
+	return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+	    "symbol dictionary specified invalid huffman table");
+	break;
+    }
+    if (flags & 0x0040) {
+        /* Custom table from referred segment */
+        huffman_params = jbig2_find_table(ctx, segment, table_index);
+        if (huffman_params == NULL) {
+            return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+                "Custom BMSIZE huffman table not found (%d)", table_index);
+        }
+        params.SDHUFFBMSIZE = jbig2_build_huffman_table(ctx, huffman_params);
+        ++table_index;
+    } else {
+	/* Table B.1 */
+	params.SDHUFFBMSIZE = jbig2_build_huffman_table(ctx,
+					&jbig2_huffman_params_A);
+    }
+    if (flags & 0x0080) {
+        /* Custom table from referred segment */
+        huffman_params = jbig2_find_table(ctx, segment, table_index);
+        if (huffman_params == NULL) {
+            return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+                "Custom REFAGG huffman table not found (%d)", table_index);
+        }
+        params.SDHUFFAGGINST = jbig2_build_huffman_table(ctx, huffman_params);
+        ++table_index;
+    } else {
+	/* Table B.1 */
+	params.SDHUFFAGGINST = jbig2_build_huffman_table(ctx,
+					&jbig2_huffman_params_A);
+    }
+  }
+
+  /* FIXME: there are quite a few of these conditions to check */
+  /* maybe #ifdef CONFORMANCE and a separate routine */
+  if (!params.SDHUFF) {
+    if (flags & 0x000c) {
+      jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+		  "SDHUFF is zero, but contrary to spec SDHUFFDH is not.");
+    }
+    if (flags & 0x0030) {
+      jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+		  "SDHUFF is zero, but contrary to spec SDHUFFDW is not.");
+    }
+  }
+
+  if (flags & 0x0080) {
+      jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+        "bitmap coding context is used (NYI) symbol data likely to be garbage!");
+  }
+
+  /* 7.4.2.1.2 */
+  sdat_bytes = params.SDHUFF ? 0 : params.SDTEMPLATE == 0 ? 8 : 2;
+  memcpy(params.sdat, segment_data + 2, sdat_bytes);
+  offset = 2 + sdat_bytes;
+
+  /* 7.4.2.1.3 */
+  if (params.SDREFAGG && !params.SDRTEMPLATE) {
+      if (offset + 4 > segment->data_length)
+	goto too_short;
+      memcpy(params.sdrat, segment_data + offset, 4);
+      offset += 4;
+  } else {
+      /* sdrat is meaningless if SDRTEMPLATE is 1, but set a value
+         to avoid confusion if anybody looks */
+      memset(params.sdrat, 0, 4);
+  }
+
+  if (offset + 8 > segment->data_length)
+    goto too_short;
+
+  /* 7.4.2.1.4 */
+  params.SDNUMEXSYMS = jbig2_get_int32(segment_data + offset);
+  /* 7.4.2.1.5 */
+  params.SDNUMNEWSYMS = jbig2_get_int32(segment_data + offset + 4);
+  offset += 8;
+
+  jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
+	      "symbol dictionary, flags=%04x, %d exported syms, %d new syms",
+	      flags, params.SDNUMEXSYMS, params.SDNUMNEWSYMS);
+
+  /* 7.4.2.2 (2) */
+  {
+    int n_dicts = jbig2_sd_count_referred(ctx, segment);
+    Jbig2SymbolDict **dicts = NULL;
+
+    params.SDINSYMS = NULL;
+    if (n_dicts > 0) {
+      dicts = jbig2_sd_list_referred(ctx, segment);
+      params.SDINSYMS = jbig2_sd_cat(ctx, n_dicts, dicts);
+    }
+    if (params.SDINSYMS != NULL) {
+      params.SDNUMINSYMS = params.SDINSYMS->n_symbols;
+    } else {
+     params.SDNUMINSYMS = 0;
+    }
+  }
+
+  /* 7.4.2.2 (4) */
+  if (!params.SDHUFF) {
+      int stats_size = params.SDTEMPLATE == 0 ? 65536 :
+	params.SDTEMPLATE == 1 ? 8192 : 1024;
+      GB_stats = jbig2_new(ctx, Jbig2ArithCx, stats_size);
+      memset(GB_stats, 0, stats_size);
+      if (params.SDREFAGG) {
+	stats_size = params.SDRTEMPLATE ? 1 << 10 : 1 << 13;
+	GR_stats = jbig2_new(ctx, Jbig2ArithCx, stats_size);
+	memset(GR_stats, 0, stats_size);
+      }
+  }
+
+  if (flags & 0x0100) {
+      jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
+        "segment marks bitmap coding context as retained (NYI)");
+  }
+
+  segment->result = (void *)jbig2_decode_symbol_dict(ctx, segment,
+				  &params,
+				  segment_data + offset,
+				  segment->data_length - offset,
+				  GB_stats, GR_stats);
+#ifdef DUMP_SYMDICT
+  if (segment->result) jbig2_dump_symbol_dict(ctx, segment);
+#endif
+
+  if (params.SDHUFF) {
+      jbig2_release_huffman_table(ctx, params.SDHUFFDH);
+      jbig2_release_huffman_table(ctx, params.SDHUFFDW);
+      jbig2_release_huffman_table(ctx, params.SDHUFFBMSIZE);
+      jbig2_release_huffman_table(ctx, params.SDHUFFAGGINST);
+  }
+
+  /* todo: retain or free GB_stats, GR_stats */
+
+  return (segment->result != NULL) ? 0 : -1;
+
+ too_short:
+  return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
+		     "Segment too short");
+}
