[GRASS-dev] d.legend uses a static buffer for `map='

  As of a recent SVN HEAD, `d.legend' uses a static buffer to
  store the value of the `map' option. It's therefore impossible
  to pass raster names more than 63 bytes long to `d.legend'.
  Since I don't see why a static buffer may be necessary here, I
  suggest the following (yet untested) patch.

diff --git a/display/d.legend/main.c b/display/d.legend/main.c
index d827d04..324012d 100644
--- a/display/d.legend/main.c
+++ b/display/d.legend/main.c
@@ -39,7 +39,7 @@ int main( int argc, char **argv )
{
   char *mapset ;
   char buff[512];
- char map_name[64] ;
+ const char *map_name;
   char window_name[64] ;
   int black ;
   int cats_num ;
@@ -188,7 +188,7 @@ int main( int argc, char **argv )
   if (G_parser(argc, argv))
     exit(EXIT_FAILURE);

- strcpy(map_name, opt1->answer) ;
+ map_name = opt1->answer;

         hide_catstr = hidestr->answer; /* note hide_catstr gets changed and re-read below */
         hide_catnum = hidenum->answer;

PS. I've seen a number of other uses of static buffers scattered across
  the code, leading to both potential limits and crashes, which
  I'm on the way to investigate.

Hi,

applied in trunk, thanks. Regards, Martin

http://trac.osgeo.org/grass/changeset/29587

2008/1/7, Ivan Shmakov <ivan@theory.asu.ru>:

        As of a recent SVN HEAD, `d.legend' uses a static buffer to
        store the value of the `map' option. It's therefore impossible
        to pass raster names more than 63 bytes long to `d.legend'.
        Since I don't see why a static buffer may be necessary here, I
        suggest the following (yet untested) patch.

diff --git a/display/d.legend/main.c b/display/d.legend/main.c
index d827d04..324012d 100644
--- a/display/d.legend/main.c
+++ b/display/d.legend/main.c
@@ -39,7 +39,7 @@ int main( int argc, char **argv )
{
        char *mapset ;
        char buff[512];
- char map_name[64] ;
+ const char *map_name;
        char window_name[64] ;
        int black ;
        int cats_num ;
@@ -188,7 +188,7 @@ int main( int argc, char **argv )
        if (G_parser(argc, argv))
                exit(EXIT_FAILURE);

- strcpy(map_name, opt1->answer) ;
+ map_name = opt1->answer;

         hide_catstr = hidestr->answer; /* note hide_catstr gets changed and re-read below */
         hide_catnum = hidenum->answer;

PS. I've seen a number of other uses of static buffers scattered across
        the code, leading to both potential limits and crashes, which
        I'm on the way to investigate.

_______________________________________________
grass-dev mailing list
grass-dev@lists.osgeo.org
http://lists.osgeo.org/mailman/listinfo/grass-dev

--
Martin Landa <landa.martin@gmail.com> * http://gama.fsv.cvut.cz/~landa *

Ivan Shmakov wrote:

  As of a recent SVN HEAD, `d.legend' uses a static buffer to
  store the value of the `map' option. It's therefore impossible
  to pass raster names more than 63 bytes long to `d.legend'.
  Since I don't see why a static buffer may be necessary here, I
  suggest the following (yet untested) patch.

diff --git a/display/d.legend/main.c b/display/d.legend/main.c
index d827d04..324012d 100644
--- a/display/d.legend/main.c
+++ b/display/d.legend/main.c
@@ -39,7 +39,7 @@ int main( int argc, char **argv )
{
   char *mapset ;
   char buff[512];
- char map_name[64] ;
+ const char *map_name;
   char window_name[64] ;
   int black ;
   int cats_num ;
@@ -188,7 +188,7 @@ int main( int argc, char **argv )
   if (G_parser(argc, argv))
     exit(EXIT_FAILURE);

- strcpy(map_name, opt1->answer) ;
+ map_name = opt1->answer;

         hide_catstr = hidestr->answer; /* note hide_catstr gets changed and re-read below */
         hide_catnum = hidenum->answer;

PS. I've seen a number of other uses of static buffers scattered across
  the code, leading to both potential limits and crashes, which
  I'm on the way to investigate.

There isn't necessarily a problem with using fixed-size buffers for
map names, but the size should be GNAME_MAX. Similar constants exist
for mapset names and pathnames:

  /* File/directory name lengths */
  #define GNAME_MAX 256
  #define GMAPSET_MAX 256

  #define GPATH_MAX 4096

In the specific case of d.legend, your fix is the correct one, except
that map_name needs to be "char *" not "const char *", as
G_find_cell() may modify the map name (it removes any @mapset suffix).

As this never enlarges the string, it's okay to re-use the original
buffer.

--
Glynn Clements <glynn@gclements.plus.com>

Glynn Clements <glynn@gclements.plus.com> writes:

>> As of a recent SVN HEAD, `d.legend' uses a static buffer to store
>> the value of the `map' option. It's therefore impossible to pass
>> raster names more than 63 bytes long to `d.legend'. Since I don't
>> see why a static buffer may be necessary here, I suggest the
>> following (yet untested) patch.

[...]

>> PS. I've seen a number of other uses of static buffers scattered
>> across the code, leading to both potential limits and crashes, which
>> I'm on the way to investigate.

> There isn't necessarily a problem with using fixed-size buffers for
> map names, but the size should be GNAME_MAX.

  Well, yes. It's actually strcpy () use which bothers me the
  most, and not the static buffer per se. Obviously, `d.legend'
  may accidentally get called with a `map' value larger than any
  predefined static buffer size, leading to hardly predicable
  behaviour.

  In my opinion, strncpy () should generally be preferred over
  strcpy ().

> Similar constants exist for mapset names and pathnames:

> /* File/directory name lengths */
> #define GNAME_MAX 256
> #define GMAPSET_MAX 256

  ACK, thanks for the info.

  What I'm talking about actually is the use of strcpy () in
  lib/display/list.c. Apparently, the static buffers passed to,
  e. g., D_get_cell_name () have the sizes that don't match any of
  the constants above. Consider, e. g.:

$ nl -ba display/d.colors/main.c
...
    25 int
    26 main (int argc, char **argv)
    27 {
    28 char name[128] = "";
...
    43 if (R_open_driver() == 0)
    44 {
    45 if(D_get_cell_name (name) < 0)
    46 *name = 0;
    47 R_close_driver();
    48 }
...
$

  Doesn't the above mean that `d.colors' cannot handle rasters
  with names more than 127 bytes long?

> #define GPATH_MAX 4096

  ... Though any such limits give me a hard feeling in general.
  Consider, e. g., GNU/Hurd system, which has no limits on the
  path length. Wouldn't the above involve extra effort to the
  porters?

> In the specific case of d.legend, your fix is the correct one,
> except that map_name needs to be "char *" not "const char *", as
> G_find_cell() may modify the map name (it removes any @mapset
> suffix).

  ACK. I've suspected the issues like this one. I guess, would I
  make a test build of GRASS, the compiler would issue a warning.

> As this never enlarges the string, it's okay to re-use the original
> buffer.

  I see, Martin has turned G_find_cell () into G_find_cell2 ()
  instead.

Ivan Shmakov wrote:

>> As of a recent SVN HEAD, `d.legend' uses a static buffer to store
>> the value of the `map' option. It's therefore impossible to pass
>> raster names more than 63 bytes long to `d.legend'. Since I don't
>> see why a static buffer may be necessary here, I suggest the
>> following (yet untested) patch.

[...]

>> PS. I've seen a number of other uses of static buffers scattered
>> across the code, leading to both potential limits and crashes, which
>> I'm on the way to investigate.

> There isn't necessarily a problem with using fixed-size buffers for
> map names, but the size should be GNAME_MAX.

  Well, yes. It's actually strcpy () use which bothers me the
  most, and not the static buffer per se. Obviously, `d.legend'
  may accidentally get called with a `map' value larger than any
  predefined static buffer size, leading to hardly predicable
  behaviour.

If you're worried about buffer overflows, there are probably somewhere
between a few hundred and a few thousand others to worry about.

To date, the general policy has been to use buffers which are
sufficently large for "normal" use and rely upon the user not doing
anything abnormal (like using 1000-character map names).

If you're writing a "web application" (CGI script) on top of GRASS,
validating any user-supplied data is essential.

  In my opinion, strncpy () should generally be preferred over
  strcpy ().

Using strncpy() isn't much of an improvement, as the copy won't be
NUL-terminated if it's truncated. And simplyy NUL-terminating the
string risks reading/writing the wrong file. The only safe solutions
are to either dynamically allocate all buffers, or signal a fatal
error if the source is too long.

> Similar constants exist for mapset names and pathnames:

> /* File/directory name lengths */
> #define GNAME_MAX 256
> #define GMAPSET_MAX 256

  ACK, thanks for the info.

  What I'm talking about actually is the use of strcpy () in
  lib/display/list.c. Apparently, the static buffers passed to,
  e. g., D_get_cell_name () have the sizes that don't match any of
  the constants above. Consider, e. g.:

$ nl -ba display/d.colors/main.c
...
    25 int
    26 main (int argc, char **argv)
    27 {
    28 char name[128] = "";

Right. We encourage developers to replace these with the defined
constants as they encounter them.

...
    43 if (R_open_driver() == 0)
    44 {
    45 if(D_get_cell_name (name) < 0)
    46 *name = 0;
    47 R_close_driver();
    48 }
...
$

  Doesn't the above mean that `d.colors' cannot handle rasters
  with names more than 127 bytes long?

Yes.

> #define GPATH_MAX 4096

  ... Though any such limits give me a hard feeling in general.
  Consider, e. g., GNU/Hurd system, which has no limits on the
  path length. Wouldn't the above involve extra effort to the
  porters?

Not really. It's only a problem if a path longer than that actually
occurs. 4096 characters is over 50 lines of 80 columns each. Who
actually has pathnames that long?

> In the specific case of d.legend, your fix is the correct one,
> except that map_name needs to be "char *" not "const char *", as
> G_find_cell() may modify the map name (it removes any @mapset
> suffix).

  ACK. I've suspected the issues like this one. I guess, would I
  make a test build of GRASS, the compiler would issue a warning.

Yes.

> As this never enlarges the string, it's okay to re-use the original
> buffer.

  I see, Martin has turned G_find_cell () into G_find_cell2 ()
  instead.

G_find_cell2() is preferable, but you need to ensure that there isn't
any code which relies upon the @mapset having been stripped off (e.g.
code which generates additional map names by appending a suffix).

A while back, I changed all of the libgis functions which I could find
to accept qualified, so leaving the name qualified shouldn't be a
problem so far as libgis is concerned, but there may still be problems
with other libraries and with the module itself.

--
Glynn Clements <glynn@gclements.plus.com>

Glynn Clements <glynn@gclements.plus.com> writes:

[...]

>>>> PS. I've seen a number of other uses of static buffers scattered
>>>> across the code, leading to both potential limits and crashes,
>>>> which I'm on the way to investigate.

>>> There isn't necessarily a problem with using fixed-size buffers for
>>> map names, but the size should be GNAME_MAX.

>> Well, yes. It's actually strcpy () use which bothers me the most,
>> and not the static buffer per se. Obviously, `d.legend' may
>> accidentally get called with a `map' value larger than any
>> predefined static buffer size, leading to hardly predicable
>> behaviour.

> If you're worried about buffer overflows, there are probably
> somewhere between a few hundred and a few thousand others to worry
> about.

  Perhaps.

> To date, the general policy has been to use buffers which are
> sufficently large for "normal" use and rely upon the user not doing
> anything abnormal (like using 1000-character map names).

> If you're writing a "web application" (CGI script) on top of GRASS,
> validating any user-supplied data is essential.

  I'm not considering web applications as yet. Besides, there may
  be other sources of ``potentially dangerous'' data.

  My primary concern is that the software shouldn't surprise the
  user by breaking gratuitously. In particular, I was quite
  surprised when `d.legend' has failed for a particularly long
  raster name. I had to make a copy for each of the rasters to
  make it work, and it was a hassle.

>> In my opinion, strncpy () should generally be preferred over strcpy
>> ().

> Using strncpy() isn't much of an improvement, as the copy won't be
> NUL-terminated if it's truncated. And simplyy NUL-terminating the
> string risks reading/writing the wrong file. The only safe solutions
> are to either dynamically allocate all buffers, or signal a fatal
> error if the source is too long.

  Or, if a library function is considered, it may return a kind of
  error to the caller.

  For example, I'd suggest changing D_get_cell_name () as follows:

diff --git a/lib/display/list.c b/lib/display/list.c
index 48180cf..2353092 100644
--- a/lib/display/list.c
+++ b/lib/display/list.c
@@ -99,6 +99,9 @@ int D_get_cell_name(char *name )
   if ((stat = R_pad_get_item ("cell", &list, &count)))
     return(-1) ;

+ if (strlen (list[0]) >= GNAME_MAX)
+ return -1;
+
   strcpy(name, list[0]) ;

   R_pad_freelist (list,count) ;

  (Add a warning to your taste.)

  Along with changing all its users to use static buffers of size
  GNAME_MAX. (I'd try to find them all and propose a patch soon.)

[...]

>> $ nl -ba display/d.colors/main.c
>> ...
>> 25 int
>> 26 main (int argc, char **argv)
>> 27 {
>> 28 char name[128] = "";

> Right. We encourage developers to replace these with the defined
> constants as they encounter them.

  ACK.

[...]

>>> As this never enlarges the string, it's okay to re-use the original
>>> buffer.

>> I see, Martin has turned G_find_cell () into G_find_cell2 ()
>> instead.

  ... Though `const' was stripped off the `map_name' declaration
  as well?

> G_find_cell2() is preferable, but you need to ensure that there isn't
> any code which relies upon the @mapset having been stripped off (e.g.
> code which generates additional map names by appending a suffix).

  ACK.

> A while back, I changed all of the libgis functions which I could
> find to accept qualified, so leaving the name qualified shouldn't be
> a problem so far as libgis is concerned, but there may still be
> problems with other libraries and with the module itself.

  The only uses of `map_name' in d.legend are:

G_read_colors (map_name, mapset, ...)
G_raster_map_is_fp (map_name, mapset)
G_read_cats (map_name, mapset, ...)
G_read_range (map_name, mapset, ...)
G_read_fp_range (map_name, mapset, ...)

  I guess, they're safe?

PS. Shouldn't G__open () use GPATH_MAX as well?

  One more problem with d.legend is that it labels both ends of
  the scale (and places intermediate labels at equal distances.)
  However, it may be more appropriate not to label one or both
  ends.

  Consider, e. g., raster having the range -13 .. 123. With
  d.legend placing, say, 7 labels they would be: -13, 10, 32, 55,
  78, 100, 123. While it may be preferred to have 0, 20, 40, 60,
  80, 100, 120 labelled instead (like Gnuplot would put the tics
  on the axes by default.)

  Apparently, the current labelling algorithm is implemented with
  the following:

$ nl -ba display/d.legend/main.c
   605 for(k = 0; k < steps; k++) {
   606 if(!fp) {
   607 if(!flip)
   608 tcell = min_ind + k * (double)(max_ind - min_ind)/(steps-1);
   609 else
   610 tcell = max_ind - k * (double)(max_ind - min_ind)/(steps-1);
...
   627 }
   628 else { /* ie FP map */
   629 if(hide_catnum)
   630 buff[0] = 0; /* no text */
   631 else {
   632 if(!flip)
   633 val = dmin + k * (dmax - dmin)/(steps-1);
   634 else
   635 val = dmax - k * (dmax - dmin)/(steps-1);
...
   639 }
...
   689 } /*for*/
$

  Any chances of making the Gnuplot-like algorithm available as an
  option?

  FWIW, here is the script I've used as a workaround to generate
  Gnuplot-like legends. (It depends on `gnuplot', `fig2dev' and
  `convert' being in "$PATH".)

#!/bin/bash
### r.colorbox.sh --- Color boxes for GRASS rasters -*- sh -*-

### Configuration
: ${CONVERT:=convert}
: ${D_LEGEND:=d.legend}
: ${D_MON:=d.mon}
: ${FIG2DEV:=fig2dev}
: ${GNUPLOT:=gnuplot}
: ${R_INFO:=r.info}

## FIXME: allow command line options here?
out_sfx=.eps

### Globals
name="$(basename "$0")"
verbose=yes

### Auxiliary functions

make_color_scale () {
    ## FIXME: d.mon should allow /dev/stdout as a target file
    local png="$(mktemp -t "${name}-$$.png")"
    local map="$1" out="$2"
    local prev_mon=$(${D_MON} -p \
                         | sed -e '/.*:[[:blank:]]*/!d; s///')
    GRASS_WIDTH=$[256 + 3] GRASS_HEIGHT=$[5 + 3] GRASS_TRUECOLOR=TRUE \
        GRASS_PNGFILE="$png" ${D_MON} start=PNG \
        && ${D_LEGEND} -vcs map="$map" at=0,100,0,100 \
        && ${D_MON} stop=PNG \
        || return 1
    [ -n "$prev_mon" ] && ${D_MON} select="$prev_mon"
    ## FIXME: a hack
    sleep .1s
    ${CONVERT} -crop 256x1+2+4 "$png" "$out"
    rm -f -- "$png"
}

make_gp () {
    local min="$1" max="$2"
    cat <<EOF
set terminal postscript eps
set size 1, .15
set xtics nomirror
unset ytics
unset key
set xrange [${min} : ${max}]
set yrange [2 : 3]
plot 1
EOF
}

make_fig () {
    local frame="$1"
    local grayscale="$2"
    cat <<EOF
#FIG 3.2 Produced by xfig version 3.2.5-alpha5
Portrait
Flush left
Metric
A4
100.00
Single
-2
1200 2
2 5 0 1 0 -1 50 -1 20 0.000 0 0 -1 0 0 5
  0 ${frame}
   225 225 5940 225 5940 812 225 812 225 225
2 5 0 1 0 -1 60 -1 -1 0.000 0 0 -1 0 0 5
  0 ${grayscale}
   405 338 5760 338 5760 585 405 585 405 338
EOF
}

raster_min_max () {
    local raster="$1"
    ## NB: a hack
    $R_INFO -r map="$raster" \
  | sed -e 's/.*=//' \
  | sort -g \
  | tr \\n ' '
    echo
}

### Code:

if [ "$#" -lt 2 ]; then
    echo "Usage: $name PREFIX [RASTER]..." >&2
    exit 1
fi

prefix="$1"
shift

## FIXME: improve error handling
set -e

for raster in "$@"; do
    out="${prefix}${raster}${out_sfx}"
    test "$verbose" = yes \
  && echo "* Producing ${out}..." >&2
    read min max < <(raster_min_max "$raster")
    if [ "$min" = nan -o "$max" = nan ]; then
        echo "${name}: ${raster}: Invalid data range; ignored"
        continue
    fi
    frameeps="$(mktemp -t "$name".eps-XXXXXX)"
    fig="$(mktemp -t "$name".fig-XXXXXX)"
    color_scale="$(mktemp -t "$name".ppm-XXXXXX)"
    make_color_scale "$raster" \
        "ppm:${color_scale}"
    make_gp "$min" "$max" \
  | $GNUPLOT > "$frameeps"
    make_fig "$frameeps" "$color_scale" \
  | $FIG2DEV -L eps \
  > "$out"
    rm -f -- "$color_scale" "$frameeps" "$fig"
done

### r.colorbox.sh ends here

[...]

Ivan Shmakov wrote:

>> In my opinion, strncpy () should generally be preferred over strcpy
>> ().

> Using strncpy() isn't much of an improvement, as the copy won't be
> NUL-terminated if it's truncated. And simplyy NUL-terminating the
> string risks reading/writing the wrong file. The only safe solutions
> are to either dynamically allocate all buffers, or signal a fatal
> error if the source is too long.

  Or, if a library function is considered, it may return a kind of
  error to the caller.

That requires each caller to check for errors.

  For example, I'd suggest changing D_get_cell_name () as follows:

diff --git a/lib/display/list.c b/lib/display/list.c
index 48180cf..2353092 100644
--- a/lib/display/list.c
+++ b/lib/display/list.c
@@ -99,6 +99,9 @@ int D_get_cell_name(char *name )
   if ((stat = R_pad_get_item ("cell", &list, &count)))
     return(-1) ;

+ if (strlen (list[0]) >= GNAME_MAX)
+ return -1;
+

That isn't a problem; it's reasonably safe to assume that the names
which appear in the list don't exceed that value (e.g. most
filesystems won't let you have a name longer than that).

>> $ nl -ba display/d.colors/main.c
>> ...
>> 25 int
>> 26 main (int argc, char **argv)
>> 27 {
>> 28 char name[128] = "";

> Right. We encourage developers to replace these with the defined
> constants as they encounter them.

  ACK.

Well, it's not as if anyone is going to sytematically examine the
entire GRASS source code to find such cases.

PS. Shouldn't G__open () use GPATH_MAX as well?

Yes. And GNAME_MAX and GMAPSET_MAX.

And it shouldn't be freeing the mapset either.

--
Glynn Clements <glynn@gclements.plus.com>

On Jan 8, 2008 8:18 PM, Glynn Clements <glynn@gclements.plus.com> wrote:

Ivan Shmakov wrote:
That isn't a problem; it's reasonably safe to assume that the names
which appear in the list don't exceed that value (e.g. most
filesystems won't let you have a name longer than that).

> >> $ nl -ba display/d.colors/main.c
> >> ...
> >> 25 int
> >> 26 main (int argc, char **argv)
> >> 27 {
> >> 28 char name[128] = "";
>
> > Right. We encourage developers to replace these with the defined
> > constants as they encounter them.
>
> ACK.

Well, it's not as if anyone is going to sytematically examine the
entire GRASS source code to find such cases.

> PS. Shouldn't G__open () use GPATH_MAX as well?

Yes. And GNAME_MAX and GMAPSET_MAX.

And it shouldn't be freeing the mapset either.

Here are a couple of candidates:

cd lib/gis/
grep path *.c | grep char | grep -v GPATH_MAX | grep '\['
closecell.c: char path[4096];
get_ellipse.c: char ipath[1024], *str, *str1;
get_projinfo.c: char path[1024];
get_projinfo.c: char path[1024];
get_projname.c: char path[1024], buff[1024], answer[50], *a;
get_window.c:char path[1024];
make_loc.c: char path[2048];
make_mapset.c: char path[2048];
myname.c: char path[500];
open.c: char path[1024];
remove.c: char path2[4096];

Change all of them to GPATH_MAX?

Markus

Ivan Shmakov wrote:

  One more problem with d.legend is that it labels both ends of
  the scale (and places intermediate labels at equal distances.)
  However, it may be more appropriate not to label one or both
  ends.

  Consider, e. g., raster having the range -13 .. 123. With
  d.legend placing, say, 7 labels they would be: -13, 10, 32, 55,
  78, 100, 123. While it may be preferred to have 0, 20, 40, 60,
  80, 100, 120 labelled instead (like Gnuplot would put the tics
  on the axes by default.)

I will be able to address d.legend issues when I get back to work next
week. The logic of the module is a bit of a nightmare so I like to do
it personally.

but quickly,
- Labels for smooth legends (incl FP maps) worked once upon a time.
  see r.category
- There are options for range= and labels= so you can make the breaks
wherever you want them with a small bit of math. see also use=
ps.map's automatic legend ticks may be prefered but offer much less
control.

Hamish

      ____________________________________________________________________________________
Looking for last minute shopping deals?
Find them fast with Yahoo! Search. http://tools.search.yahoo.com/newsearch/category.php?category=shopping

Markus Neteler wrote:

> > PS. Shouldn't G__open () use GPATH_MAX as well?
>
> Yes. And GNAME_MAX and GMAPSET_MAX.
>
> And it shouldn't be freeing the mapset either.

Here are a couple of candidates:

cd lib/gis/
grep path *.c | grep char | grep -v GPATH_MAX | grep '\['
closecell.c: char path[4096];
get_ellipse.c: char ipath[1024], *str, *str1;
get_projinfo.c: char path[1024];
get_projinfo.c: char path[1024];
get_projname.c: char path[1024], buff[1024], answer[50], *a;
get_window.c:char path[1024];
make_loc.c: char path[2048];
make_mapset.c: char path[2048];
myname.c: char path[500];
open.c: char path[1024];
remove.c: char path2[4096];

Change all of them to GPATH_MAX?

Yes.

More generally, anything matching:

  grep '\[[0-9][0-9][0-9]*\]'

deserves at least a cursory glance. Many of those should really be
using a #define'd constant, even if it's local to a single source
file.

--
Glynn Clements <glynn@gclements.plus.com>

On Jan 11, 2008 10:31 PM, Glynn Clements <glynn@gclements.plus.com> wrote:

Markus Neteler wrote:

> > > PS. Shouldn't G__open () use GPATH_MAX as well?
> >
> > Yes. And GNAME_MAX and GMAPSET_MAX.
> >
> > And it shouldn't be freeing the mapset either.
>
> Here are a couple of candidates:
>
> cd lib/gis/
> grep path *.c | grep char | grep -v GPATH_MAX | grep '\['
> closecell.c: char path[4096];
> get_ellipse.c: char ipath[1024], *str, *str1;
> get_projinfo.c: char path[1024];
> get_projinfo.c: char path[1024];
> get_projname.c: char path[1024], buff[1024], answer[50], *a;
> get_window.c:char path[1024];
> make_loc.c: char path[2048];
> make_mapset.c: char path[2048];
> myname.c: char path[500];
> open.c: char path[1024];
> remove.c: char path2[4096];
>
> Change all of them to GPATH_MAX?

Yes.

First step done:
http://trac.osgeo.org/grass/changeset/29670

Markus

More generally, anything matching:

        grep '\[[0-9][0-9][0-9]*\]'

deserves at least a cursory glance. Many of those should really be
using a #define'd constant, even if it's local to a single source
file.

--

Glynn Clements <glynn@gclements.plus.com>

--
Open Source Geospatial Foundation
http://www.osgeo.org/
http://www.grassbook.org/

Glynn Clements <glynn@gclements.plus.com> writes:

>>>> In my opinion, strncpy () should generally be preferred over
>>>> strcpy ().

>>> Using strncpy() isn't much of an improvement, as the copy won't be
>>> NUL-terminated if it's truncated. And simplyy NUL-terminating the
>>> string risks reading/writing the wrong file. The only safe

>>> solutions are to either dynamically allocate all buffers, or signal
>>> a fatal error if the source is too long.

>> Or, if a library function is considered, it may return a kind of
>> error to the caller.

> That requires each caller to check for errors.

  There're a plenty of cases where such checks are necessary
  regardless of the content length check being implemented.

  I'd consider it an error if, say, the value of D_get_cell_name
  () not being checked for being non-negative.

>> For example, I'd suggest changing D_get_cell_name () as follows:

>> diff --git a/lib/display/list.c b/lib/display/list.c
>> index 48180cf..2353092 100644
>> --- a/lib/display/list.c
>> +++ b/lib/display/list.c
>> @@ -99,6 +99,9 @@ int D_get_cell_name(char *name )
>> if ((stat = R_pad_get_item ("cell", &list, &count)))
>> return(-1) ;
>>
>> + if (strlen (list[0]) >= GNAME_MAX)
>> + return -1;
>> +

> That isn't a problem; it's reasonably safe to assume that the names
> which appear in the list don't exceed that value (e.g. most
> filesystems won't let you have a name longer than that).

  I'd have that check anyway. However, yes, it's certainly of low
  priority, and I won't insist on the change above being applied;
  I'd rather keep this patch around in case anyone will ever need
  it.

[...]

Glynn Clements <glynn@gclements.plus.com> writes:

[...]

> More generally, anything matching:

> grep '\[[0-9][0-9][0-9]*\]'

> deserves at least a cursory glance. Many of those should really be
> using a #define'd constant, even if it's local to a single source
> file.

  Could you review the attached patch?

  Things I didn't change with this patch:

  * buffers related to SQL commands, because I know no appropriate
    Cpp macro for these;

  * buffers related to command lines to be passed to system (),
    since I believe that using a specific library to construct the
    arguments lists to be passed to an `exec' family function
    directly would be a better solution; (check the `man-db'
    source [1] for a suitable facility);

  * misused G_warning (), etc.; like:

   char BUF[...];
   sprintf (BUF, FORMAT, ...);
   G_warning (BUF);

    instead of:

   G_warning (FORMAT, ...);

    (the documentation should emphasize the first argument being
    the printf ()-style format string, and /not/ the message);
    there're just too many of these and I hope to handle them with
    a script;

  * use of gets ()-like functions to discard everything on stdin
    until the next newline; a separate function is needed there;

  * all the other things I've missed or had no time to look at
    (nviz/ and most of ps.map/ in particular.)

[1] http://www.chiark.greenend.org.uk/~cjwatson/bzr/man-db/trunk/

(attachments)

buffers.patch.gz (7.88 KB)

Ivan Shmakov <ivan@theory.asu.ru> writes:
Glynn Clements <glynn@gclements.plus.com> writes:

>> More generally, anything matching:

>> grep '\[[0-9][0-9][0-9]*\]'

>> deserves at least a cursory glance. Many of those should really be
>> using a #define'd constant, even if it's local to a single source
>> file.

[...]

> * misused G_warning (), etc.; like:

> char BUF[...]; sprintf (BUF, FORMAT, ...); G_warning (BUF);

> instead of:

> G_warning (FORMAT, ...);

> (the documentation should emphasize the first argument being the
> printf ()-style format string, and /not/ the message); there're just
> too many of these and I hope to handle them with a script;

  The following command should catch most of these:

$ grep --context=5 --recurse --include=\*.c -E \
      'G_(warning|fatal_error|([[:alnum:]_]+_)?message)[[:blank:]]*([[:alnum:]_]+)' \
      .

  BTW, the correct pattern for issuing a string message is:

   G_message ("%s", string);

  not:

   G_message (string);

[...]

Ivan Shmakov <ivan@theory.asu.ru> writes:

[...]

> BTW, the correct pattern for issuing a string message is:

> G_message ("%s", string);

> not:

> G_message (string);

> [...]

  May I suggest the following patch?

--- lib/gis/error.c 2008-01-03 14:15:58.000000000 +0600
+++ lib/gis/error.c 2008-01-12 23:14:50.000000000 +0600
@@ -93,6 +93,16 @@
                         time_t, const char *);
static int log_error (const char *, int);

+static int
+vfprint_error (int type, const char *template, va_list ap)
+{
+ char buffer[2000]; /* G_asprintf does not work */
+
+ vsprintf (buffer, template, ap);
+
+ /* . */
+ return print_error (buffer, type);
+}

/*!
  * \fn void G_message (const char *msg,...)
@@ -105,15 +115,11 @@
*/
void G_message (const char *msg,...)
{
- char buffer[2000]; /* G_asprintf does not work */
- va_list ap;
-
     if( G_verbose() >= G_verbose_std() ) {
+ va_list ap;
   va_start(ap, msg);
- vsprintf(buffer,msg,ap);
+ vfprint_error (MSG, msg, ap);
   va_end(ap);
-
- print_error (buffer,MSG);
     }
}

@@ -130,15 +136,11 @@
*/
void G_verbose_message (const char *msg, ...)
{
- char buffer[2000]; /* G_asprintf does not work */
- va_list ap;
-
     if( G_verbose() > G_verbose_std() ) {
+ va_list ap;
   va_start(ap, msg);
- vsprintf(buffer,msg,ap);
+ vfprint_error (MSG, msg, ap);
   va_end(ap);
-
- G_message(buffer);
     }
}

@@ -158,15 +160,11 @@
*/
void G_important_message(const char *msg, ...)
{
- char buffer[2000]; /* G_asprintf does not work */
- va_list ap;
-
     if( G_verbose() > G_verbose_min() ) {
+ va_list ap;
   va_start(ap, msg);
- vsprintf(buffer,msg,ap);
+ vfprint_error (MSG, msg, ap);
   va_end(ap);
-
- print_error (buffer,MSG);
     }
}

@@ -194,14 +192,11 @@
*/
int G_fatal_error (const char *msg,...)
{
- char buffer[2000]; /* No novels to the error logs, OK? */
     va_list ap;

     va_start(ap,msg);
- vsprintf(buffer,msg,ap);
+ vfprint_error (ERR, msg, ap);
     va_end(ap);
-
- print_error (buffer,ERR);
     
     exit (EXIT_FAILURE);
}
@@ -221,15 +216,13 @@
*/
int G_warning (const char *msg, ...)
{
- char buffer[2000];
     va_list ap;

     if (no_warn) return 0;

     va_start(ap,msg);
- vsprintf(buffer,msg,ap);
+ vfprint_error (WARN, msg, ap);
     va_end(ap);
- print_error (buffer,WARN);

     return 0;
}

Ivan Shmakov wrote:

  Could you review the attached patch?

Seems fine.

  Things I didn't change with this patch:

  * buffers related to SQL commands, because I know no appropriate
    Cpp macro for these;

I would suggest just adding e.g.:

  #define SQL_COMMAND_MAX 4000

to the top of the file, then using that. Although it doesn't provide
consistency between modules, it makes it easier to find such constants
if they're at the top of the file.

  * buffers related to command lines to be passed to system (),
    since I believe that using a specific library to construct the
    arguments lists to be passed to an `exec' family function
    directly would be a better solution; (check the `man-db'
    source [1] for a suitable facility);

I've already done some work on eliminating the use of system(); see
lib/gis/spawn.c. I'd rather see more code modified to use those
functions than just tinkering with the buffer size.

  * misused G_warning (), etc.; like:

   char BUF[...];
   sprintf (BUF, FORMAT, ...);
   G_warning (BUF);

    instead of:

   G_warning (FORMAT, ...);

    (the documentation should emphasize the first argument being
    the printf ()-style format string, and /not/ the message);
    there're just too many of these and I hope to handle them with
    a script;

Note that those functions are tagged with:

  __attribute__((format(printf,1,2)));

With gcc, you can use the option:

`-Wformat-nonliteral'
     If `-Wformat' is specified, also warn if the format string is not a
     string literal and so cannot be checked, unless the format function
     takes its format arguments as a `va_list'.

to identify problematic cases (although --with-nls will interfere with
this).

--
Glynn Clements <glynn@gclements.plus.com>

Ivan Shmakov wrote:

> BTW, the correct pattern for issuing a string message is:

> G_message ("%s", string);

> not:

> G_message (string);

> [...]

  May I suggest the following patch?

Seems reasonable enough.

--- lib/gis/error.c 2008-01-03 14:15:58.000000000 +0600
+++ lib/gis/error.c 2008-01-12 23:14:50.000000000 +0600
@@ -93,6 +93,16 @@
                         time_t, const char *);
static int log_error (const char *, int);

+static int
+vfprint_error (int type, const char *template, va_list ap)
+{
+ char buffer[2000]; /* G_asprintf does not work */
+
+ vsprintf (buffer, template, ap);

It would be nice to use vsnprintf(), but we would need to check that
it's available (it's not in C89).

--
Glynn Clements <glynn@gclements.plus.com>

On Mon, 14 Jan 2008, Glynn Clements wrote:

It would be nice to use vsnprintf(), but we would need to check that
it's available (it's not in C89).

I haven't used IRIX for a while - so won't be complaining about stuff like that being used without checks any more :slight_smile:

Paul

Glynn Clements <glynn@gclements.plus.com> writes:

[...]

>> Things I didn't change with this patch:

>> * buffers related to SQL commands, because I know no appropriate Cpp
>> macro for these;

> I would suggest just adding e.g.:

> #define SQL_COMMAND_MAX 4000

> to the top of the file, then using that. Although it doesn't provide
> consistency between modules, it makes it easier to find such
> constants if they're at the top of the file.

  There're just too many files for this macro to be defined.
  Could there be a separate header to define the macros like this
  one?

[...]

>> * misused G_warning (), etc.; like:

[...]

>> there're just too many of these and I hope to handle them with a
>> script;

> Note that those functions are tagged with:

> __attribute__((format(printf,1,2)));

> With gcc, you can use the option:

> `-Wformat-nonliteral' If `-Wformat' is specified, also warn if the
> format string is not a string literal and so cannot be checked,
> unless the format function takes its format arguments as a `va_list'.

> to identify problematic cases (although --with-nls will interfere
> with this).

  It much more a problem to get them changed automatically than to
  get them identified.