3

Fortify client gives the error and recommendations for string builder,

problem code:

StringBuilder sb=new StringBuilder();    
sb.append(request.getParameter("id"));
sb.append(request.getParameter("name"));
sb.append(request.getParameter("question"));
sb.append(request.getParameter("answer"));

Fortify Error:

User-controlled data is appended to a StringBuilder instance initialized with the default constructor

Appending user-controlled data to a StringBuilder or StringBuffer instance initialized with the default backing character array size (16) can cause the application to consume large amounts of heap memory while resizing the underlying array to fit user's data. When data is appended to a StringBuilder or StringBuffer instance, the instance will determine if the backing character array has enough free space to store the data. If the data does not fit, the StringBuilder or StringBuffer instance will create a new array with a capacity of at least double the previous array size, and the old array will remain in the heap until it is garbage collected. Attackers can use this implementation detail to execute a Denial of Service (DoS) attack.

Fortify Recommendations: Initialize the StringBuilder or StringBuffer with an initial capacity of the expected appended data size to reduce the number of times the backing array is resized. Check the size of the data before appending it to a StringBuilder or StringBuffer instance.

...
private final int BUFFER_CAPACITY = 5200;
StringBuilder sb = new StringBuilder(BUFFER_CAPACITY);
...
final String lineSeparator = System.lineSeparator();
String[] labels = request.getParameterValues("label");
for (String label : labels) {
if (label.length() + lineSeparator.length() + sb.length()<= sb.capacity()) {
sb.append(label).append(lineSeparator);
} else {
// Handle error
}
}

Queries on the problem statement and recommendation :

  1. If the garbage collectors free it's memory , how the attacker can cause denial of service attack? does it applicable ?

  2. My requirement to store the dynamic data range of 0 to 12000 max characters in string builder , so if i use 12000 characters to initialize the string builder , then if my input has only 100 character string means , remaining 11900 length of memory is not required . so in that case do i really needs to set the max of characters in String Builder capacity ? or i can go with length validation for my input parameters only with default constructor?

what will be the better solution for this problem?

Mohan
  • 35
  • 6
  • 2
    garbage collector only clears memory for items that already no longer referenced in the program logic. however you should not rely that the gc will always fire after an object dereferenced. that is why it says, the old array might stay in the memory for long. the DoS attack is very possible if you accumulate unknown amount of strings (e.g. streaming string from tcp connection until the connection closes). – Bagus Tesa May 04 '22 at 13:51
  • i'm interested in this question given its pretty hard to define "what the correct buffer size is" given we should treat fortify's parameter as a black untouchable holy box. in the past there are question regarding this kind of [issue](https://stackoverflow.com/a/51428654). there is no trivial way to set the buffer size, but if we set too small or too large, fortify will complain (again, *sigh*). – Bagus Tesa May 04 '22 at 14:13
  • 1
    If your problem code really looks like shown, just use `request.getParameter("id") + request.getParameter("name") + request.getParameter("question") + request.getParameter("answer")` – Holger May 10 '22 at 13:36
  • @holger, i believe you have missed the second point (last paragrap). the string is in range of 0 to 12000 characters. i'm not sure if we will get performance penalty for using `+`. – Bagus Tesa May 11 '22 at 07:17
  • 1
    @BagusTesa prior to Java 9, the `+` operator will compile to *exactly the same code* as the manual `StringBuilder` use shown in this question. The advantage is, without seeing the `StringBuilder`, no-one will start a discussion about its initial capacity. When you are using Java 9 or newer and `javac`, the generated code will leave the actual work to the `StringConcatFactory` provided by the runtime, which will (in the default setup of the reference implementation) be even better, because it solves the issue of the initial capacity. To sum it up, there is no performance penalty when using `+`. – Holger May 11 '22 at 07:23
  • @Holger *"prior to Java 9, the + operator will compile to exactly the same code as the manual `StringBuilder` use shown in this question"* - if my memory serves me right, fortify static code analyzer can decompile java files and scan that code instead in which it will raise `StringBuilder` issue once again. I believe op using `StringBuilder` for the fact his/her/it source has arbitrary length - unknown at compile time. That code is just an example as working with govt agency wont let you share code online easily. but op silence is pain, i wish he clarified more. i have this exact problem too. – Bagus Tesa May 12 '22 at 12:00

3 Answers3

5

This is nonsense.

There might be related issue in that, for instance:

  • the input data is huge (though it appears to come from request headers, so should already be limited upstream)
  • the individual input is empty, but there are many instances of it, creating all the overhead for sensible values with little input data - so don't go allocating a larger than default initial capacity if there is any possibility that you might not use it!
  • with insufficient care, the generated string could be ambiguous or subject to an injection vulnerability.

StringBuilder will at least double capacity whenever the current buffer is exhausted to amortise the cost to O(n). The StringBuilder may well be larger than the input data. Partly this may be an overallocation, perhaps twice the length of this original, if it is not turned into a String. Also the original is typically 1-byte per character, but Java has 2-byte chars. If the input is compressed, the explosion in size can be surprising, particularly if multiply compressed.

I recommend having a go at DoSing your own application, although I have seen surprisingly little evidence that application-level DoS attacks are as yet common in the wild.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    `StringBuilder` supports 1 byte per character, just like `String`. Only when one of the appended strings contains at least one non-latin character, the data has to be inflated. However, then, the resulting string also requires two bytes per character. – Holger May 11 '22 at 11:09
  • @Holger I had forgotten the optiisations for US ASCII(?) `String`s. However, only one character needs to be out of range to turn all those 1-byte characters into two, so this doesn't really matter. I can't remember the details, though with a suitable character encoding (insist on UTF-8), 1-byte characters can have Unicode values above 255. – Tom Hawtin - tackline May 11 '22 at 18:29
  • 1
    The optimization works if all characters are within Iso-Latin-1. So every other 8 bit encoding, not ASCII or Latin-1, may have characters outside the range. UTF-8, however, does not count, as it encodes all non-ascii codepoints with at least two bytes. The biggest source of surprise might be the widely used Windows-1252 which looks much like Latin-1, often confused with it, but has a few characters outside the range. Also, illegal characters are replaced by U+FFFD so even decoding ASCII can result in an inflated string. And yes, having just one of them is enough to inflate the entire result. – Holger May 12 '22 at 07:42
  • @TomHawtin-tackline *"I recommend having a go at DoSing your own application, although I have seen surprisingly little evidence that application-level DoS attacks are as yet common in the wild."* - it might not be common for average app, however for critical system may differ. could we focus on the "good enough" buffer size to stop `fortify` from complaining? this is why i'm asking for "reputable source" - experienced people in dealing with `fortify` reports. – Bagus Tesa May 12 '22 at 12:20
  • 2
    @BagusTesa then, you placed the bounty on the wrong question. This question was “is there a real issue?”, not “how can I make fortify happy?” You can not demand from answerers of this question to focus on your entirely different question. By the way, if you have a critical system that would suffer from long parameter strings, you should consider that those strings are already in memory even before this string concatenation. Trying to address the issue at this point is way too late. – Holger May 12 '22 at 14:02
  • @Holger you are right, asking for the second point in the question is actually pointless. – Bagus Tesa May 12 '22 at 14:15
2

Answering the two direct questions:

"If the garbage collectors free it's memory , how the attacker can cause denial of service attack? does it applicable ?"

  1. Realistically the attacker can't. Something will almost certainly break first if you are accepting arbitrarily large input data. Garbage collectors in the JVM are very fast and great at cleaning up short-lived data.

  2. You could set the initial buffer size to 12K, but that would also put you at risk from an attacker. Suppose they send many very tiny requests, knowing that you will always allocate 12K for each request. Further more, let's say that they can slow down sending the many, tiny requests, so that you can't free up the buffers until the request completes. This DOES put you at risk of OOMing, because the buffers can't be freed until all the data is present.

Without knowing more about the attack vector, fortify is being overly cautious here. If I were you, I would silence the warning on this method, or just pick a buffer size equals to the StringBuilder default of 16. The main reason to pick a larger value is if you normally get larger values.

Carl Mastrangelo
  • 5,970
  • 1
  • 28
  • 37
1

For q2 - guard the size of incoming strings - parameter values here - and like the code snippet in Fortify's suggestion, reject too long input strings. For an expected input of 0 to 12000 chars, I would assign, say, 100 in the sb constructor, and add that suggested check of input string length.

void append(StringBuilder sb, String text, int limit) {
  if (sb.length() + text.length() <= limit) {
    sb.append(text);
    return;
  }
  handleError(...);
}

int LIMIT = 12000;

StringBuilder sb=new StringBuilder(100);    
append(sb,request.getParameter("id"), LIMIT);
...
  • is this a well tested approach against `fortify`? – Bagus Tesa May 07 '22 at 08:43
  • @mohan's original question above seems a "defense" one, while this seems an attack one... A related one was asked [here](https://security.stackexchange.com/questions/257585/what-can-you-do-against-fortify-source-stack-overflow). – Doron Cohen May 08 '22 at 09:25
  • 1
    do keep in mind [tag:fortify-source] is a different stuff altogether. – Bagus Tesa May 12 '22 at 12:22