0

My code is supposed to scan a username and password and make sure that they aren't greater than the max amount. If they are, it should re-prompt the user to enter something.

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>

#define MAXNAME 30

int main() {
    char username_input[MAXNAME];
    char password_input[MAXNAME];

    username_input[MAXNAME - 1] = '\0';
    password_input[MAXNAME - 1] = '\0';

    printf("Please enter your username: ");
    while (scanf("%s", username_input) != 1 || strlen(username_input) > MAXNAME) {
        printf("Improper input.\nPlease enter your username: ");
    }

    printf("Please enter your password: ");
    while (scanf("%s", password_input) != 1 || strlen(password_input) > MAXNAME) {
        printf("Improper input.\nPlease enter your password: ");
    }
    return 0;
}

The problem that I am facing is that whenever one of these while loops are entered, I will get the re-prompt fine and I will be able to continue with the code, but once i hit return 0 I will have an exception thrown.

The exception reads: Run-Time Check Failure #2 - Stack around the variable 'username_input' was corrupted.

I have tried using scanf_s and fgets() as well, those haven't seemed to work.

Toasty
  • 3
  • 1
  • 4
    If `strlen(username_input) > MAXNAME` is ever true you've corrupted memory which is what the error is telling you. Use a width specifier with `scanf`, like `%29s`. – Retired Ninja Apr 04 '23 at 01:55
  • 1
    See [How to prevent `scanf()` causing a buffer overflow in C?](https://stackoverflow.com/q/1621394/15168) – Jonathan Leffler Apr 04 '23 at 02:48

2 Answers2

0

Try this:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#define MAX_LEN 30

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf("Please enter your %s: ", descr);
        if (scanf("%31[^\n]", buf) == 1 && strlen(buf) <= MAX_LEN) {
            break;
        }
        printf("Improper input.\n");
        scanf("%*[^\n]");
        scanf("%*c");
    } while (!feof(stdin));
    scanf("%*c");
}

int main()
{
    char username_input[MAX_LEN + 2], password_input[MAX_LEN + 2];
    read_ident("username", username_input);
    read_ident("password", password_input);
}

I love scanf, but you might find that fgets is more convenient. See reference.

The idea of my code above is to read 31 characters at most, before the end of line. If you don’t get more than 30 characters you’re done. Otherwise, %*[^\n] skips all characters before the end of line (if any: if the end of line immediately follows, this attempt to read will fail, this is why we must have two consecutive scanf), then %*c skips the end of line character (“line feed”).

If you don’t want to or cannot hard code the length of your buffer, you could write something like that:

char format[16];
sprintf("%%%d[^\n]", MAX_LEN + 1);
scanf(format, buf);

As said by @Retired Ninja, your code crashes because you don’t tell scanf how many characters it should read at most, so it may overflow your buffer. Reading strings with scanf shouldn’t normally be done without specifying a width (unless you don’t care about safety).

Here is how to do with fgets:

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf("Please enter your %s: ", descr);
        fgets(buf, MAX_LEN + 2, stdin);
        size_t nb_read_chars = strlen(buf);
        if (nb_read_chars >= 2 && buf[nb_read_chars - 1] == '\n') {
            buf[nb_read_chars - 1] = '\0';
            break;
        }
        printf("Improper input.\n");
        if (nb_read_chars >= 2) {
            while (fgetc(stdin) != '\n');
        }
    } while (!feof(stdin));
}

I do prefer scanf.

scanf_s won’t help because they trigger an exception if there is an error, this is not what you want to do (and I think you will rarely want to do that).

0

Being a C programmer is happy until one day we need to handle variant length input from user. All libc APIs for 'reading user input' take only 'fix buffer' -- as you can see like scanf, fgets ... , but we don't know how long user will input before he/she actually inputs, so we have no idea 'how large buffer we should make for those scanf/fgets'.

Usually there are 2 options. One is to limited user input, and discard any data exceed the limitation, and another is 'dynamic allocation' -- we first allocate a chunk of buffer then 'append' user input in minimal unit to the buffer, if we would run out buffer space, we enlarge it before we put futher more into it.

The approach is somehow verbose, we should make it 'reusable'. Here is a sample:

#include <stdio.h>
#include <string.h>
#include <malloc.h>

#define DB_BLOCK_SIZE (100)

typedef struct tagDynaBuf {
    char * buf;
    unsigned int capacity;
    unsigned int size;
} DynaBuf;

void dynabuf_init(DynaBuf * db){
    memset((void*)db, 0, sizeof(*db));
}

void dynabuf_release(DynaBuf * db){
    if (!db){
        return;
    }

    if (!db->buf){
        return;
    }

    free(db->buf);
    memset((void*)db, 0, sizeof(*db));
}

int dynabuf_append_char(DynaBuf * db, int c){

    if (!db){
        fprintf(stderr,"%u: 'db' was null.\n", __LINE__);
        return -1;
    }

    // 1. Let's make sure we have enough space in out buf.
    if (!db->buf){
        // malloc for the 1st time
        db->buf = malloc(DB_BLOCK_SIZE);
        if (!db->buf ){
            fprintf(stderr,"%u: failed in 1st malloc.\n", __LINE__);
            return -2;
        }

        db->capacity = DB_BLOCK_SIZE;
        db->size = 0;
    }

    if (db->capacity == db->size ){
        // Ohoh, we ran out of our buf space, let's make it larger.
        char * new_buf = realloc( db->buf, db->capacity + DB_BLOCK_SIZE);
        if (!new_buf)
        {
            fprintf(stderr,"%u: failed in enlarging buf for %u.\n", __LINE__, db->capacity + DB_BLOCK_SIZE);
            return -3;
        }

        db->capacity += DB_BLOCK_SIZE;
        db->buf = new_buf;
    }

    // Now we can accecpt the new comer -- 'c' -- with confidence.

    db->buf[db->size] = c;
    db->size ++;

    return 0;
}

int main(int argc, char ** argv){
    int ret = 0;
    DynaBuf buf;
    dynabuf_init(&buf);

    printf("Enter text as long as you can (end with ctrl-D), I will try my best to handle them:\n");

    while (1){
        int c = fgetc(stdin);
        if ( EOF == c)
        {
            break;
        }

        if ( dynabuf_append_char(&buf, c)){
            ret = 1;
            goto EXIT;
        }
    }

    // make our buf \0 terminated.
    if ( dynabuf_append_char(&buf, 0)){
        ret = 2;
        goto EXIT;
    }

    printf("\n\nThank you! I got:\n%s\n", buf.buf);

EXIT:
    dynabuf_release(&buf);
    return ret;
}

You can even test it as cat {a large text file} | ./aout. Hope it could help.

grizzlybears
  • 492
  • 2
  • 9