6

I have:

#define MAX_STR_LEN 100

and I want to put into scanf pattern so I can control the string length:

scanf("%100[^\n]s",sometext)

I tried:

scanf("%MAX_STR_LEN[^\n]s",sometext)
scanf("%"MAX_STR_LEN"[^\n]s",sometext)
scanf("%",MAX_STR_LEN,"[^\n]s",sometext)

And it didn't work. I just want to avoid buffer overflow since "sometext" is allocated with malloc(MAX_STR_LEN)...

Any ideas?

Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
tomdavies
  • 1,896
  • 5
  • 22
  • 32
  • Discussed [here](http://stackoverflow.com/questions/9457325/how-to-use-sscanf-correctly-and-safely). – meaning-matters Jun 26 '13 at 08:11
  • How can you use `MAX_STR_LEN` within a format string? – amulous Jun 26 '13 at 08:20
  • @amulous, since it's strict ANSI C, I don't really have access to real string variable. I need to do: char *somestring and then malloc... – tomdavies Jun 26 '13 at 08:21
  • Probably is too late , but I did this function just to solve that problem. https://github.com/tsw1985/Gscanf – TSW1985 Dec 28 '14 at 23:34
  • 1
    Aside from the answers here, I want to point out that if you use `"%100s"` the allocation needs to be 101 bytes, so in your example, you need `malloc(MAX_STR_LEN+1)`, or `"%99s"`. – Tim Sylvester Aug 04 '15 at 17:25

5 Answers5

10

I wasn't happy with any of these solutions, so I researched further, and discovered GNU GCC macro stringification

which can be used as:

#define XSTR(A) STR(A)
#define STR(A) #A
#define MAX_STR_LEN 100
scanf("%"XSTR(MAX_STR_LEN)"[^\n]s", sometext)

Maybe VS2010 offers something similar?

Damion
  • 116
  • 1
  • 2
7

I just want to avoid buffer overflow

Then don't use scanf(). At all.

If you are scanning lines of text, don't #define MAX_STR either. You can haz LINE_MAX in <limits.h> (if you are targeting POSIX compatible systems):

char buf[LINE_MAX];
fgets(buf, sizeof(buf), stdin);

should do the trick.

4

As almost everyone says, it is better to use fgets(..., stdin) to handle this problem.

In the following link, I have suggested a safe and correct technique that let you to replace scanf() by a safer method, by means of a solid macro:

A macro that safely replaces scanf()

The macro I have proposed (working with compatible C99 compilers) is safe_scanf(), as shown in the following program:

#include <stdio.h>
#define safe_scanf(fmt, maxb, ...) { \
    char buffer[maxb+1] = { [maxb - 1] = '\0' }; \
    fgets(buffer, maxb+1, stdin); \
    if ((buffer[maxb - 1] != '\0') && (buffer[maxb - 1] != '\n')) \
        while(getchar() != '\n') \
           ; \
    sscanf(buffer, fmt, __VA_ARGS__); \
  }

#define MAXBUFF 20     

int main(void) {
   int x; float f;      
   safe_scanf("%d %g", MAXBUFF+1, &x, &f);
   printf("Your input was: x == %d\t\t f == %g",  x, f);
   return 0;
}  

You would have to tune the value of MAXBUFF according to your needs...
Although the macro safe_scanf() is pretty solid,
there are some weakness in using the macro approach:
Missing type-checking for parameters, missing "return" values (which hardly differs from the "true" scanf() function, that returns an int, with valuable information for error checking), and so on.
All that problems have solution, but it is part of another topic...

Maybe, the most exact solution is to define a function my_scanf() with variable number of parameters, by invoking the stdarg.h library, joint to a combination of fgets() and vsscanf(). Here you have the code:

#include <stdio.h>
#include <stdarg.h>

int my_scanf(const char* fmt, const unsigned int maxbuff, ...) {
    va_list ptr;
    int ret;

    if (maxbuff <= 0)
       return EOF; /* Bad size for buffer[] */

    char buffer[maxbuff+1];
    buffer[maxbuff-1] = '\0';  /* Quick buffer cleaning... */

    if (fgets(buffer, maxbuff+1, stdin) == NULL)
       return EOF; /* Error detected */
    else {
        if ((buffer[maxbuff-1] != '\n') && (buffer[maxbuff-1] != '\0'))
            /* Condition logically equivalent to:
                   fgets() has not reached an '\n'
            */
            while (getchar() != '\n')
               ; /* "Flushing" stdin... */

        va_start(ptr, maxbuff);
        ret = vsscanf(buffer, fmt, ptr);
        va_end(ptr);
        return ret;
    }    
}

#define MAXBUFF 20
int main(void) {
   int x; 
   float z;
   int scanf_ret = my_scanf("%d %g", MAXBUFF, &x, &z);
   printf("\nTest:\n x == %d\n z == %g\n scanfret == %d", x, z, scanf_ret);
   getchar();   
   return 0;   
}

The function my_scanf() has the prototype

int my_scanf(const char* fmt, const int maxbuff, ...);

It accepts a format string fmt that behaves in the same way as any other scanf()-like does.
The 2nd parameter is the maximum number of chars that will be effectively accepted from the standard input (keyboard).
The return value is an int, which is EOF if maxbuff has no sense, or well some input error happened. If a non-negative value is returned, it is the same that would be returned by the standard functions sscanf() or vsscanf().

Inside the function, maxbuff is incremented in 1, because fgets() makes some room for an additional '\0' character.
Non-positive values of maxbuff are immediatelly discarded.
fgets() will read a string is read from stdin (keyboard) with room for at most maxbuff characters, including '\n'.
If the user has entered a very long string, then it will bu truncated, and some kind of "flush" mechanism is necessary in order to discard all the characters to the next '\n' (ENTER). If not, the next keyboard reading could have older characters, not desired at all.
The condition for "flushing" is that fgets() has not reached '\n' after reading stdin.
This is the case if, and only if, buffer[maxbuff - 1] is not equal to '\0' nor '\n'.
(Check it!)
Finally, an appropiate combination of stdarg.h macros and the function vsscanf() is employed to process the variable list of parameters.

Community
  • 1
  • 1
pablo1977
  • 4,281
  • 1
  • 15
  • 41
0

Recommend the fgets(buffer, sizeof(buffer), stdin) approach.

If you still want to use scanf() you can create its format at runtime.

#define MAX_STR_LEN 100
char format[2 + sizeof(size_t)*3 + 4 + 1];  // Ugly magic #
sprintf(format, " %%%zu[^\n]", (size_t) MAX_STR_LEN);
scanf(format, sometext);

or re-define MAX_STR_LEN to be a string

#define MAX_STR_LEN "100"
scanf(" %" MAX_STR_LEN "[^\n]", sometext);

Still recommend the fgets().
Note fgets() will put leading spaces and the trailing \n in your buffer whereas " %[^\n]" will not.
BTW: the trailing s in your formats is not likely doing what you think.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
-3

How about

scanf("%.*[^\n]s", MAX_STR_LEN, sometext)
Neil
  • 11,059
  • 3
  • 31
  • 56
  • Please, pretty please, ***No!*** –  Jun 26 '13 at 08:12
  • 3
    Recommend you re-read `*` usage in `scanf()`. I am certain you will want to change your post. In printf() usage, `*` reads an integer for size, but not in `scanf()`. `*` means "An optional starting asterisk indicates that the data is to be read from the stream but ignored". A very different function. – chux - Reinstate Monica Jun 30 '13 at 06:33