Closed Bug 743056 Opened 12 years ago Closed 12 years ago

nsPrintfCString should always require a buffer size

Categories

(Core :: XPCOM, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla14

People

(Reporter: vladan, Assigned: benjamin)

References

Details

Attachments

(14 files, 2 obsolete files)

3.33 KB, patch
Details | Diff | Splinter Review
2.04 KB, patch
mounir
: review+
Details | Diff | Splinter Review
1.88 KB, patch
bzbarsky
: review+
Details | Diff | Splinter Review
4.42 KB, patch
Details | Diff | Splinter Review
6.46 KB, patch
roc
: review+
Details | Diff | Splinter Review
5.94 KB, patch
Details | Diff | Splinter Review
2.20 KB, patch
mayhemer
: review+
Details | Diff | Splinter Review
1.79 KB, patch
Details | Diff | Splinter Review
1.17 KB, patch
jrmuizel
: review-
Details | Diff | Splinter Review
1.36 KB, patch
bholley
: review+
Details | Diff | Splinter Review
3.24 KB, patch
mak
: review+
Details | Diff | Splinter Review
20.02 KB, patch
mak
: review+
Details | Diff | Splinter Review
14.14 KB, patch
justin.lebar+bug
: review+
Details | Diff | Splinter Review
14.27 KB, patch
Details | Diff | Splinter Review
Currently, nsPrintfCString supplies two constructors: one takes a buffer size argument specifying the maximum output string length and one which (silently) assumes a maximum output length of 15 bytes.

The second version of the constructor is problematic because the 15 byte max behaviour is likely not what the nsPrintfCString users are expecting. For example, in bug 741821, :espindola found that SQL strings prefixed with comments were getting truncated by nsPrintfCString, resulting in no-op SQL statements and NULL pointer fun.

We should remove the problematic constructor and rewrite all calls to it to use the other c'tor and explicitly specify an output buffer length.

Assigning to froydnj at Taras's suggestion.
The trivial patch to remove the default size 15 constructor.

This and all subsequent patches will of course be rolled up for commit; the separation is just for ease of review.
Attachment #612921 - Flags: review?(benjamin)
I'll describe my methodology for the subsequent patches once, here, so I can refer to a single location and avoiding spamming the bug.

By and large, I simply changed:

nsPrintfCString foo(fmt, ...);

to

nsPrintfCString foo(15, fmt, ...);

as that gives the behavior things had before.  Exceptions:

- Where 15 looked "obviously" bad, I bumped it up.  Generally anything with two or more %letter sequences got a bump.  If the string was already pushing 15 characters, I bumped it up too.
- I used a simple AppendInt where appropriate, which may result in slightly longer code.
- Likewise for AppendPrintf, but this is rare.

I took the liberty of deleting dead nsPrintfCString.h includes where appropriate.

Patch spam incoming.
Attached patch content/ changesSplinter Review
Attachment #612927 - Flags: review?(mounir)
Attached patch dom/ changesSplinter Review
Attachment #612928 - Flags: review?(bzbarsky)
Attached patch toolkit/ changes (obsolete) — Splinter Review
Attachment #612929 - Flags: review?(dtownsend+bugmail)
Attached patch layout/ changesSplinter Review
Attachment #612931 - Flags: review?(smontagu)
Attached patch widget/ changesSplinter Review
Attachment #612932 - Flags: review?(roc)
Attached patch netwerk/ changesSplinter Review
Attachment #612933 - Flags: review?(bsmith)
Attachment #612934 - Flags: review?(honzab.moz)
Attached patch xpcom/ changesSplinter Review
Attachment #612935 - Flags: review?(benjamin)
Attached patch gfx/ changesSplinter Review
Attachment #612936 - Flags: review?(jmuizelaar)
Attachment #612936 - Attachment is patch: true
Attachment #612937 - Flags: review?(bobbyholley+bmo)
Comment on attachment 612928 [details] [diff] [review]
dom/ changes

r=me
Attachment #612928 - Flags: review?(bzbarsky) → review+
Attached patch storage/ changesSplinter Review
Attachment #612938 - Flags: review?(mak77)
Attachment #612937 - Flags: review?(bobbyholley+bmo) → review+
Comment on attachment 612938 [details] [diff] [review]
storage/ changes

Review of attachment 612938 [details] [diff] [review]:
-----------------------------------------------------------------

::: storage/src/VacuumManager.cpp
@@ +230,5 @@
>    // vacuum, if they are executed in the same transaction.
>    nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
> +
> +  nsCAutoString pragma_str;
> +  pragma_str.Append(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size = ");

You may probably initialize the autostring to a value, instead of creating it empty and then appending immediately
Attachment #612938 - Flags: review?(mak77) → review+
Comment on attachment 612929 [details] [diff] [review]
toolkit/ changes

Review of attachment 612929 [details] [diff] [review]:
-----------------------------------------------------------------

::: toolkit/components/places/nsNavHistory.cpp
@@ +3969,5 @@
>      mClause.Append(' ');
>      if (!mQueryIndex)
>        mClause.Append(aParam);
>      else
> +      mClause += nsPrintfCString(15, "%s%d", aParam, mQueryIndex);

we are quite near the limit here (I've found a couple 14 sized strings, so it's working by luck), I'd prefer a bit larger buffer, or we should rather append.
May we add a debug-only fatal assert on the string length vs the buffer size? Even if that may we a bit more expensive, would be debug-only and at least some sort of protection.
(In reply to Marco Bonardo [:mak] from comment #16)
> ::: toolkit/components/places/nsNavHistory.cpp
> @@ +3969,5 @@
> >      mClause.Append(' ');
> >      if (!mQueryIndex)
> >        mClause.Append(aParam);
> >      else
> > +      mClause += nsPrintfCString(15, "%s%d", aParam, mQueryIndex);
> 
> we are quite near the limit here (I've found a couple 14 sized strings, so
> it's working by luck), I'd prefer a bit larger buffer, or we should rather
> append.

This would be a good place to AppendPrintf; I should have done that in the first place.  I'll update the patch.
Comment on attachment 612928 [details] [diff] [review]
dom/ changes

Actually, for that first one, using 23 might be better, since a PRUint32 _could_ end up 10 digits long....
Attachment #612934 - Flags: review?(honzab.moz) → review+
Comment on attachment 612929 [details] [diff] [review]
toolkit/ changes

Marco, can you look over the places pieces of this too please, the rest looks fine
Attachment #612929 - Flags: review?(mak77)
Attachment #612929 - Flags: review?(dtownsend+bugmail)
Attachment #612929 - Flags: review+
sure, the next version that will implement comment 18 :)
(In reply to Boris Zbarsky (:bz) from comment #19)
> Actually, for that first one, using 23 might be better, since a PRUint32
> _could_ end up 10 digits long....

Would you object to just changing that to:

  ver.Append('/')
  ver.AppendInt(major)
  ver.Append('.')
  ver.AppendInt(minor)

?
Not at all.  Looks good to me.
Attached patch toolkit/ changesSplinter Review
Implementing comment 18 and handing off to mak.
Attachment #612929 - Attachment is obsolete: true
Attachment #612929 - Flags: review?(mak77)
Attachment #612976 - Flags: review?(mak77)
(In reply to Marco Bonardo [:mak] from comment #17)
> May we add a debug-only fatal assert on the string length vs the buffer
> size? Even if that may we a bit more expensive, would be debug-only and at
> least some sort of protection.

As in, "assert if the producing string from printf is longer than the provided buffer"?  There doesn't seem to be a good way to do that; PR_{v,}snprintf don't tell you if they overflowed the buffer.  {v,}snprintf *do* signal that, however.  I'd like to ditch NSPR bits, but whether {v,}snprintf are portable and don't cause weird behavior changes...
(In reply to Nathan Froyd (:froydnj) from comment #25)
> As in, "assert if the producing string from printf is longer than the
> provided buffer"?  There doesn't seem to be a good way to do that;
> PR_{v,}snprintf don't tell you if they overflowed the buffer.  {v,}snprintf
> *do* signal that, however.  I'd like to ditch NSPR bits, but whether
> {v,}snprintf are portable and don't cause weird behavior changes...

I see, maybe worth a follow-up in nspr to investigate adding overflow detection?
Otherwise, we may "guess" from the initial string and the kind of bound data if there's opportunity to go over the size, would be quite far from perfect, but it's scary that we don't have any sort of check here :(
Comment on attachment 612976 [details] [diff] [review]
toolkit/ changes

Review of attachment 612976 [details] [diff] [review]:
-----------------------------------------------------------------

::: toolkit/components/places/nsNavHistory.cpp
@@ +249,5 @@
>             "FROM moz_bookmarks b_t "
>             "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent  "
>             "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" "
>             "AND t_t.parent = ") +
> +           nsPrintfCString(15, "%lld", aTagsFolder) + NS_LITERAL_CSTRING(" "

nit: int64 may go up to 20 chars, though it's really unlikely we may ever hit that, also cause it would cause much bigger pain in js with the 53 bits limit. This may also be valid below for %d, where you usually accounted 10 chars, but not everywhere. Again, it's mostly cases where we are unlikely to go over 10, so I don't care that much.

::: toolkit/xre/nsAppRunner.cpp
@@ +1928,5 @@
>  
>    nsCOMPtr<nsIToolkitProfile> newProfile;
>    // Make the new profile "default-" + the time in seconds since epoch for uniqueness.
>    nsCAutoString newProfileName("default-");
> +  newProfileName.AppendInt(PR_Now() / 1000);

may this implicit cast warn?
Attachment #612976 - Flags: review?(mak77) → review+
Why can't we just make this do the right thing and always allocate enough space?  The comment in the file says it's not intended for long strings, but it's clearly being used that way.

We should just write an equivalent of vasprintf and be done with it.  In the meantime, perhaps we should make nsPrintfCString debug-assert when you exceed its bounds?
> In the meantime, perhaps we should make nsPrintfCString debug-assert when you exceed its bounds?

Sorry, I should have read the bug first.  :)  It's a stupid bug in NSPR that their snprintf doesn't return the size!  But let's please not stop at this bug in fixing this.
Comment on attachment 612927 [details] [diff] [review]
content/ changes

Review of attachment 612927 [details] [diff] [review]:
-----------------------------------------------------------------

::: content/html/document/src/nsHTMLDocument.cpp
@@ +2151,1 @@
>        + NS_LITERAL_CSTRING("/")

I think this should just use Append...
I'm pretty sure that we don't need any of the other changes on this bug if we just fix nsPrintfCstring to allocate, which I've done using the PR_vsxprintf function. I also converted nsPrintfCString to be a true fixed-string class, now that we can do that basically for free.
Attachment #613321 - Flags: review?(justin.lebar+bug)
https://tbpl.mozilla.org/?tree=Try&rev=c50ce57fe330 submitted to try.
Assignee: nfroyd → benjamin
Status: NEW → ASSIGNED
Comment on attachment 613321 [details] [diff] [review]
Do the obvious thing, allocate, rev. 1

>+++ b/xpcom/string/public/nsPrintfCString.h
>@@ -48,40 +48,42 @@
>    *
>    *   myCStr += nsPrintfCString("%f", 13.917);
>    *     // ...a general purpose substitute for |AppendFloat|
>    *
>    * For longer patterns, you'll want to use the constructor that takes a length
>    *
>    *   nsPrintfCString(128, "%f, %f, %f, %f, %f, %f, %f, %i, %f", x, y, z, 3.2, j, k, l, 3, 3.1);
>    *
>-   * Exceding the default size (which you must specify in the constructor, it is not determined)
>-   * causes an allocation, so avoid that.  If your formatted string exceeds the allocated space, it is
>-   * cut off at the size of the buffer, no error is reported (and no out-of-bounds writing occurs).
>-   * This class is intended to be useful for numbers and short
>-   * strings, not arbitrary formatting of other strings (e.g., with %s).  There is currently no
>-   * wide version of this class, since wide |printf| is not generally available.  That means
>-   * to get a wide version of your formatted data, you must, e.g.,
>+   * There is currently no wide version of this class, since wide |printf| is
>+   * not generally available.  That means to get a wide version of your
>+   * formatted data, you must, e.g.,
>    *
>    *   CopyASCIItoUTF16(nsPrintfCString("%f", 13.917"), myStr);
>    *
>    * That's another good reason to avoid this class for anything but numbers ... as strings can be
>    * much more efficiently handled with |NS_LITERAL_[C]STRING| and |nsLiteral[C]String|.
>    */

I'd like us to clean up the comment about how we shouldn't use this class for anything but numbers, since that is consistent with neither its new design nor how it's used.  :)

>-// though these classes define a fixed buffer, they do not set the F_FIXED
>-// flag.  this is because they are not intended to be modified after they have
>-// been constructed.  we could change this but it would require adding a new
>-// class to the hierarchy, one that both this class and nsCAutoString would
>-// inherit from.  for now, we populate the fixed buffer, and then let the
>-// nsCString code treat the buffer as if it were a dependent buffer.
>-
> nsPrintfCString::nsPrintfCString( const char_type* format, ... )
>-  : string_type(mLocalBuffer, 0, F_TERMINATED)
>+  : nsFixedCString(mLocalBuffer, kLocalBufferSize)
>   {

Looks like this will cause nsTFixedString to do a strlen on mLocalBuffer.

|nsFixedCString(mLocalBuffer, kLocalBufferSize, 0)| should be safe, but perhaps we should just inherit from nsCAutoString?
Attachment #613321 - Flags: review?(justin.lebar+bug) → review-
(In reply to Benjamin Smedberg  [:bsmedberg] from comment #31)
> I'm pretty sure that we don't need any of the other changes on this bug if
> we just fix nsPrintfCstring to allocate, which I've done using the
> PR_vsxprintf function. I also converted nsPrintfCString to be a true
> fixed-string class, now that we can do that basically for free.

If we're going to do that, why not just nuke nsPrintfCString and adjust places to use AppendPrintf instead?
> If we're going to do that, why not just nuke nsPrintfCString and adjust places to use AppendPrintf 
> instead?

AppendPrintf currently allocates, but we could definitely change that.  (Also, a short-lived allocation like this is not necessarily the end of the world.)
This moves all of the interesting logic into the nsTSubstring::AppendPrintf methods and just makes nsPrintfCString a sugaring syntax.
Attachment #613321 - Attachment is obsolete: true
Attachment #613346 - Flags: review?(justin.lebar+bug)
Also, I didn't just inherit from nsCAutoString because the builtin buffer sizes were different. I don't think it matters much, though...
Comment on attachment 612936 [details] [diff] [review]
gfx/ changes

I too would prefer AppendPrintf here.
Attachment #612936 - Flags: review?(jmuizelaar) → review-
Comment on attachment 613346 [details] [diff] [review]
Do the obvious thing, allocate, rev. 2

>-    private:
>-      char_type  mLocalBuffer[ kLocalBufferSize + 1 ];
>+      char_type  mLocalBuffer[ kLocalBufferSize ];
>   };
> 
>+
>+

Nit: extra whitespace
Attachment #613346 - Flags: review?(justin.lebar+bug) → review+
Comment on attachment 612927 [details] [diff] [review]
content/ changes

Review of attachment 612927 [details] [diff] [review]:
-----------------------------------------------------------------

::: content/html/document/src/nsHTMLDocument.cpp
@@ +2146,5 @@
>    mDocumentURI->GetSpec(originalSpec);
>  
>    // Generate the wyciwyg url
>    url = NS_LITERAL_CSTRING("wyciwyg://")
> +      + nsPrintfCString(15, "%d", gWyciwygSessionCnt++)

gWyciwygSessionCnt would be 10 digits long max so I guess you could use 10 instead of 15 here. 
In addition, you could use AppendInt instead of nsPrintfCString as Ms2ger pointed out. But I'm fine either way.
Attachment #612927 - Flags: review?(mounir) → review+
Attachment #612921 - Flags: review?(benjamin)
Attachment #612931 - Flags: review?(smontagu)
Attachment #612933 - Flags: review?(bsmith)
Attachment #612935 - Flags: review?(benjamin)
got r=jlebar over IRC for this slight modification: NSPR hands stufffunc the terminating null,  and our strings don't want that
https://hg.mozilla.org/integration/mozilla-inbound/rev/a3f9ee8bb745

I don't think we need to bother with most of the rest of these patches: were there any correctness changes included instead of just changing the calling patterns?
https://hg.mozilla.org/mozilla-central/rev/a3f9ee8bb745
Status: ASSIGNED → RESOLVED
Closed: 12 years ago
Resolution: --- → FIXED
Attachment #614436 - Attachment is patch: true
(In reply to Benjamin Smedberg  [:bsmedberg] from comment #42)
> I don't think we need to bother with most of the rest of these patches: were
> there any correctness changes included instead of just changing the calling
> patterns?

There were, but only because of too-short specified lengths.  If we don't use the length now, then all reason for any of the patches is gone.
Component: String → XPCOM
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: