Sur la base des commentaires et des réponses, il semble y avoir trois approches:
- Écrire une version personnalisée de
getline()
, éventuellement à l'aide de l' std::istream::getline()
membre interne pour obtenir les caractères réels.
- L'utilisation d'un filtrage de la mémoire tampon pour limiter la quantité de données potentiellement reçues.
- Au lieu de lire un
std::string
, utilisez une chaîne de l'instanciation d'un allocateur de limiter la quantité de mémoire stockée dans la chaîne.
Toutes les suggestions sont venus avec code. Cette réponse fournit le code pour toutes les approches un peu de la discussion des trois approches. Avant d'entrer dans les détails de mise en œuvre, il est d'abord utile de rappeler qu'il y a plusieurs choix de ce qui se passerait si une trop longue d'entrée est reçu:
- La lecture d'un trop long peut entraîner une réussite en lecture partielle de la ligne, c'est à dire, la chaîne résultante contient le lire le contenu et le flux n'a pas tous les indicateurs d'erreur de jeu. Ce faisant, toutefois, qu'il n'est pas possible de faire la distinction entre une ligne de frapper exactement la limite, ou trop long. Car la limite est quelque peu arbitraire de toute façon il n'a probablement pas vraiment d'importance, cependant.
- La lecture d'un trop long de la ligne pourrait être considéré comme un échec (c'est à dire, le paramètre
std::ios_base::failbit
et/ou std::ios_base::bad_bit
), et, depuis la lecture a échoué, le rendement d'une chaîne vide. Ce qui donne une chaîne vide, évidemment, empêche potentiellement en regardant la chaîne de lire jusqu'à présent pour peut-être voir ce qu'il se passe.
- La lecture d'un trop long de ligne pourrait fournir l'partielle de la ligne de lecture et également définir les indicateurs d'erreur sur le flux. Cela semble raisonnable comportement à la fois de détecter qu'il y a quelque chose, et aussi de fournir les données d'entrée pour le potentiel d'inspection.
Bien qu'il existe de multiples exemples de code, la mise en œuvre d'une version limitée de l' getline()
déjà, ici en est une autre! Je pense que c'est plus simple (quoique peut-être plus lente; les performances peuvent être traités, le cas échéant), qui conserve également l' std::getline()
s interface: utiliser le flux de l' width()
de communiquer une limite (peut-être en prenant width()
en compte est une prolongation raisonnable std::getline()
):
template <typename cT, typename Traits, typename Alloc>
std::basic_istream<cT, Traits>&
safe_getline(std::basic_istream<cT, Traits>& in,
std::basic_string<cT, Traits, Alloc>& value,
cT delim)
{
typedef std::basic_string<cT, Traits, Alloc> string_type;
typedef typename string_type::size_type size_type;
typename std::basic_istream<cT, Traits>::sentry cerberos(in);
if (cerberos) {
value.clear();
size_type width(in.width(0));
if (width == 0) {
width = std::numeric_limits<size_type>::max();
}
std::istreambuf_iterator<char> it(in), end;
for (; value.size() != width && it != end; ++it) {
if (!Traits::eq(delim, *it)) {
value.push_back(*it);
}
else {
++it;
break;
}
}
if (value.size() == width) {
in.setstate(std::ios_base::failbit);
}
}
return in;
}
Cette version de l' getline()
est utilisé comme std::getline()
mais quand il semble raisonnable de limiter la quantité de données à lire, l' width()
est définie, par ex.:
std::string line;
if (safe_getline(in >> std::setw(max_characters), line)) {
// do something with the input
}
Une autre approche est d'utiliser un système de filtrage de la mémoire tampon pour limiter le montant de l'apport: le filtre serait tout simplement compter le nombre de caractères traités et limiter la quantité à un nombre suffisant de caractères. Cette approche est en fait plus facile appliqué à un ensemble de flux que d'une ligne individuelle: lors du traitement d'une seule ligne, le filtre ne pouvez pas simplement obtenir des tampons complète de caractères dans le flux sous-jacent, car il n'existe pas de moyen fiable pour mettre les personnages en arrière. La mise en œuvre d'un tampon version est toujours simple, mais probablement pas très efficace:
template <typename cT, typename Traits = std::char_traits<char> >
class basic_limitbuf
: std::basic_streambuf <cT, Traits> {
public:
typedef Traits traits_type;
typedef typename Traits::int_type int_type;
private:
std::streamsize size;
std::streamsize max;
std::basic_istream<cT, Traits>* stream;
std::basic_streambuf<cT, Traits>* sbuf;
int_type underflow() {
if (this->size < this->max) {
return this->sbuf->sgetc();
}
else {
this->stream->setstate(std::ios_base::failbit);
return traits_type::eof();
}
}
int_type uflow() {
if (this->size < this->max) {
++this->size;
return this->sbuf->sbumpc();
}
else {
this->stream->setstate(std::ios_base::failbit);
return traits_type::eof();
}
}
public:
basic_limitbuf(std::streamsize max,
std::basic_istream<cT, Traits>& stream)
: size()
, max(max)
, stream(&stream)
, sbuf(this->stream->rdbuf(this)) {
}
~basic_limitbuf() {
std::ios_base::iostate state = this->stream->rdstate();
this->stream->rdbuf(this->sbuf);
this->stream->setstate(state);
}
};
Cette mémoire tampon est déjà configuré pour insérer lui-même lors de la construction et supprimer lui-même à la destruction. Qui est, il peut être utilisé simplement comme ceci:
std::string line;
basic_limitbuf<char> sbuf(max_characters, in);
if (std::getline(in, line)) {
// do something with the input
}
Il serait facile d'ajouter un manipulateur de la configuration de la limite, trop. Un avantage de cette approche est qu'aucun de la lecture de code a besoin d'être touché si la taille totale des flux pourrait être limitée: le filtre pourrait être mis en place juste après la création du flux. Quand il n'est pas nécessaire de retirer le filtre, le filtre peut aussi utiliser un tampon qui pourrait grandement améliorer la performance.
La troisième approche proposée est d'utiliser un std::basic_string
avec un allocateur personnalisé. Il y a deux aspects qui sont un peu maladroits dans l'allocateur approche:
- La chaîne en cours de lecture, en réalité, a un type qui n'est pas immédiatement convertibles en
std::string
(même s'il n'est pas difficile de faire la conversion).
- La taille maximale de la matrice peut facilement être limité, mais la chaîne va en avoir plus ou moins aléatoire de taille plus petite que: lorsque le flux d'échec de l'allocation d'une exception est levée et il n'y a pas de tentative de pousser la chaîne par une taille plus petite.
Voici le code nécessaire pour un allocateur de limiter la taille allouée:
template <typename T>
struct limit_alloc
{
private:
std::size_t max_;
public:
typedef T value_type;
limit_alloc(std::size_t max): max_(max) {}
template <typename S>
limit_alloc(limit_alloc<S> const& other): max_(other.max()) {}
std::size_t max() const { return this->max_; }
T* allocate(std::size_t size) {
return size <= max_
? static_cast<T*>(operator new[](size))
: throw std::bad_alloc();
}
void deallocate(void* ptr, std::size_t) {
return operator delete[](ptr);
}
};
template <typename T0, typename T1>
bool operator== (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return a0.max() == a1.max();
}
template <typename T0, typename T1>
bool operator!= (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return !(a0 == a1);
}
L'allocation serait utilisé quelque chose comme ceci (le code se compile bien avec une version récente de bruit , mais pas avec gcc):
std::basic_string<char, std::char_traits<char>, limit_alloc<char> >
tmp(limit_alloc<char>(max_chars));
if (std::getline(in, tmp)) {
std::string(tmp.begin(), tmp.end());
// do something with the input
}
En résumé, il y a plusieurs approche chacune avec son propre petit inconvénient mais chaque raisonnablement viable pour l'objectif de limiter les attaques par déni de service basé sur trop de lignes:
- À l'aide d'une version personnalisée de l'
getline()
signifie que la lecture de code doit être changé.
- En utilisant une mesure de la mémoire tampon est lent, sauf si la totalité du flux de la taille peut être limitée.
- À l'aide d'un allocateur personnalisé donne moins de contrôle et nécessite quelques modifications à la lecture du code.