[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-games-devel
Subject:    Re: [Kde-games-devel] Re: KHighscore on multiple user systems
From:       Nicolas Hadacek <hadacek () kde ! org>
Date:       2003-05-21 9:38:09
[Download RAW message or body]

On Monday 12 May 2003 11:16 am, Oswald Buddenhagen wrote:
> On Mon, May 12, 2003 at 12:14:23PM -0400, George Staikos wrote:
> >   Dropping the gid gains you nothing because any buffer overflow
> >   anywhere in the game will allow the user to regain the gid.
>
> yep, and it's the exactly same feature nicolas' code relied upon ... so
> either way "my" solution is better, as he admitted.

> one more person to point to kdebase/kdm/backend/dm.c:StorePid() :)

yeah that proved handy... is this really working for NFS ?

Here is my next (and hopefully final) try: see the attached patch and the 
KFileLock class (useful in kdelibs ??).

now at program start, I am just opening a file descriptor on the system-wide 
highscore file and then immediately and definitely dropping the effective 
group id with "setregid" (if I understood correctly the man page). An exploit 
can then only give you access to the game highscore file. Hopefully not a big 
deal :)

See you,
Nicolas

["khighscore2.diff" (text/x-diff)]

Index: khighscore.h
===================================================================
RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.h,v
retrieving revision 1.7
diff -u -2 -d -p -b -r1.7 khighscore.h
--- khighscore.h	6 Jan 2002 18:05:53 -0000	1.7
+++ khighscore.h	22 May 2003 03:00:28 -0000
@@ -2,4 +2,5 @@
     This file is part of the KDE games library
     Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de)
+    Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
 
     This library is free software; you can redistribute it and/or
@@ -27,10 +28,12 @@
 
 class KConfig;
-
+class KFileLock;
+class KRawConfig;
 class KHighscorePrivate;
+
 /**
  * This is the KDE class for saving and reading highscore tables. It offers the
  * possibility for system-wide highscore tables (configure with e.g.
- * --enable-highscore=/var/games) and a theoretically unlimited number of
+ * --enable-highscore-dir=/var/games) and a theoretically unlimited number of
  * entries.
  *
@@ -43,11 +46,11 @@ class KHighscorePrivate;
  * </pre>
  * Note that it doesn't really matter if you use "0" or "1" as the first entry
- * of the list as long as your program always uses the same for the first entry.
- * I recommend to use "1", as several convenience methods use this.
+ * of the list as long as your program always uses the same for the first
+ * entry. I recommend to use "1", as several convenience methods use this.
  *
- * You can also specify different groups using @ref setHighscoreGroup. Just like
- * the keys mentioned above the groups behave like groups in @ref KConfig but
- * are prefixed with "KHighscore_". The default group is just "KHighscore". You
- * might use this e.g. to create different highscore tables like
+ * You can also specify different groups using @ref setHighscoreGroup. Just
+ * like the keys mentioned above the groups behave like groups in @ref KConfig
+ * but are prefixed with "KHighscore_". The default group is just "KHighscore".
+ * You might use this e.g. to create different highscore tables like
  * <pre>
  * table->setHighscoreGroup("Easy");
@@ -68,6 +71,7 @@ class KHighscorePrivate;
  * </pre>
  *
- * Also note that yout MUST NOT mark the key or the group for translation! I.e. \
                don't use
- * i18n() for the keys or groups! Here is the code to read the above written entry:
+ * Also note that you MUST NOT mark the key or the group for translation! I.e.
+ * don't use i18n() for the keys or groups! Here is the code to read the above
+ * written entry:
  * <pre>
  * QString firstName = highscore->readEntry(0, "name");
@@ -81,5 +85,72 @@ class KHighscore : public QObject
 	Q_OBJECT
 public:
+        /** @obsolete
+         * Constructor. The highscore file is forced to be local to support
+         * games using the old behaviour.
+         */
 	KHighscore(QObject* parent = 0);
+
+        /** @since 3.2
+         * Constructor.
+         *
+         * @param forceLocal if true, the local highscore file is used even
+         * when the configuration has been set to use a system-wide file. This
+         * is convenient for converting highscores from legacy applications.
+         */
+        KHighscore(bool forceLocal, QObject *parent);
+
+        /** @since 3.2
+         * Read the current state of the highscore file. Remember that when
+         * it's not locked for writing, this file can change at any time.
+         * (This method is only useful for a system-wide highscore file).
+         */
+        void readCurrentConfig();
+
+        /** @since 3.2
+         * This method open the system-wide highscore file using the effective
+         * group id of the game executable (which should be "games"). The
+         * effective group id is completely dropped afterwards.
+         *
+         * Note: this method should be called in main() before creating a
+         * KApplication and doing anything else (KApplication checks that the
+         * program is not suid/sgid and will exit the program for security
+         * reason if it is the case).
+         */
+        static void init(const char *appname);
+
+        /** @since 3.2
+         * Lock the system-wide highscore file for writing (does nothing and
+         * return true if the local file is used).
+         * You should perform writing without GUI interaction to avoid
+         * blocking and don't forget to unlock the file as soon as possible
+         * with @ref writeAndUnlock().
+         *
+         * If the config file cannot be locked,
+         * the method waits for 1 second and, if it failed again, displays
+         * a message box asking for retry or cancel.
+         * @param widget used as the parent of the message box.
+         *
+         * @return false on error or if the config file is locked by another
+         * process. In such case, the config stays read-only.
+         */
+        bool lockForWriting(QWidget *widget = 0);
+
+        /** @since 3.2
+         * Effectively write and unlock the system-wide highscore file
+         * (@see lockForWriting).
+         * If using a local highscore file, it will sync the config.
+         */
+        void writeAndUnlock();
+
+        /** @since 3.2
+         * @return true if the highscore file is locked or if a local
+         * file is used.
+         */
+        bool isLocked() const;
+
+        /**
+         * Destructor.
+         * If necessary, write and unlock the highscore file.
+         */
 	~KHighscore();
 
@@ -188,4 +259,7 @@ public:
 	bool hasTable() const;
 
+        /** @obsolete
+         * This does the same as writeAndUnlock().
+         */
 	void sync();
 
@@ -201,6 +275,6 @@ public:
 	/**
 	 * @return The currently used group. This doesn't contain the prefix
-	 * ("KHighscore_") but the same as @ref setHighscoreGroup uses. The default is
-	 * QString::null
+	 * ("KHighscore_") but the same as @ref setHighscoreGroup uses. The
+         * default is QString::null
 	 **/
 	const QString& highscoreGroup() const;
@@ -215,11 +289,16 @@ protected:
 	/**
 	 * @return A pointer to the @ref KConfig object to be used. This is
-	 * either kapp->config() (default) or a @ref KSimpleConfig object (if
-	 * you configured with --enable-highscore)
+	 * either kapp->config() (default) or a @ref KSimpleConfig object for
+         * a system-wide highscore file.
 	 **/
 	KConfig* config() const;
 
+        void init(bool forceLocal);
+
 private:
 	KHighscorePrivate* d;
+
+        static KFileLock *_lock; // lock on system-wide highscore file
+        static KRawConfig *_config; // config for system-wide highscore file
 };
 
Index: khighscore.cpp
===================================================================
RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.cpp,v
retrieving revision 1.15
diff -u -2 -d -p -b -r1.15 khighscore.cpp
--- khighscore.cpp	25 Feb 2003 01:03:18 -0000	1.15
+++ khighscore.cpp	22 May 2003 03:00:28 -0000
@@ -2,4 +2,5 @@
     This file is part of the KDE games library
     Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de)
+    Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
 
     This library is free software; you can redistribute it and/or
@@ -21,10 +22,20 @@
 */
 
+#include <unistd.h>
+#include <sys/file.h>
+
 #include <kapplication.h>
 #include <ksimpleconfig.h>
 #include <kglobal.h>
+#include <kstdguiitem.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kstaticdeleter.h>
 
 #include "khighscore.h"
 #include "config.h" // HIGHSCORE_DIRECTORY is defined here (or not)
+#include "kconfigrawbackend.h"
+#include "kfilelock.h"
 
 #define GROUP "KHighscore"
@@ -33,26 +44,117 @@ class KHighscorePrivate
 {
 public:
-	KHighscorePrivate()
-	{
-		mConfig = 0;
-	}
+    KHighscorePrivate() {}
 
-	KConfig* mConfig;
 	QString group;
+    bool     global;
 };
 
-KHighscore::KHighscore(QObject* parent) : QObject(parent)
+KFileLock *KHighscore::_lock = 0;
+KRawConfig *KHighscore::_config = 0;
+static KStaticDeleter<KFileLock> lockSD;
+static KStaticDeleter<KRawConfig> configSD;
+
+
+KHighscore::KHighscore(QObject* parent)
+    : QObject(parent)
+{
+    init(true);
+}
+
+KHighscore::KHighscore(bool forceLocal, QObject* parent)
+    : QObject(parent)
+{
+    init(forceLocal);
+}
+
+void KHighscore::init(bool forceLocal)
 {
  d = new KHighscorePrivate;
+#ifdef HIGHSCORE_DIRECTORY
+    d->global = !forceLocal;
+    if ( d->global && _lock==0 )
+        kdFatal(11002) << "KHighscore::init should be called before!!" << endl;
+#else
+    d->global = false;
+    Q_UNUSED(forceLocal);
+#endif
+    readCurrentConfig();
+}
 
+bool KHighscore::isLocked() const
+{
+    return (d->global ? _lock->isLocked() : true);
 }
 
-KHighscore::~KHighscore()
+void KHighscore::readCurrentConfig()
 {
-// not necessary, as KConfig destructor should handle this
- sync();
- if (d->mConfig) {
-	delete d->mConfig;
+    if ( d->global ) _config->reparseConfiguration();
+}
+
+void KHighscore::init(const char *appname)
+{
+#ifdef HIGHSCORE_DIRECTORY
+    const QString filename =  QString::fromLocal8Bit("%1/%2.scores")
+                              .arg(HIGHSCORE_DIRECTORY).arg(appname);
+    int fd = open(filename.local8Bit(), O_RDWR);
+    if ( fd<0 ) kdFatal(11002) << "cannot open global highscore file \""
+                               << filename << "\"" << endl;
+    lockSD.setObject(_lock, new KFileLock(fd));
+    configSD.setObject(_config, new KRawConfig(fd, true)); // read-only
+
+    // drop the effective gid
+    int gid = getgid();
+    setregid(gid, gid);
+#else
+    Q_UNUSED(appname);
+#endif
+}
+
+bool KHighscore::lockForWriting(QWidget *widget)
+{
+    if ( isLocked() ) return true;
+
+    bool first = true;
+    for (;;) {
+        kdDebug(11002) << "try locking" << endl;
+        // lock the highscore file (it should exist)
+        int result = _lock->lock();
+        bool ok = ( result==0 );
+        kdDebug(11002) << "locking system-wide highscore file res="
+                       <<  result << " (ok=" << ok << ")" << endl;
+        if (ok) {
+            readCurrentConfig();
+            _config->setReadOnly(false);
+            return true;
  }
+
+        if ( !first ) {
+            KGuiItem item = KStdGuiItem::cont();
+            item.setText(i18n("Retry"));
+            int res = KMessageBox::warningContinueCancel(widget, i18n("Cannot access \
the highscore file. Another user is probably currently writing to it."), \
QString::null, item, "ask_lock_global_highscore_file"); +            if ( \
res==KMessageBox::Cancel ) break; +        } else sleep(1);
+        first = false;
+    }
+    return false;
+}
+
+void KHighscore::writeAndUnlock()
+{
+    if ( !d->global ) {
+        kapp->config()->sync();
+        return;
+    }
+    if ( !isLocked() ) return;
+
+    kdDebug(11002) << "unlocking" << endl;
+    _config->sync(); // write config
+    _lock->unlock();
+    _config->setReadOnly(true);
+}
+
+KHighscore::~KHighscore()
+{
+    writeAndUnlock();
  delete d;
 }
@@ -60,20 +162,10 @@ KHighscore::~KHighscore()
 KConfig* KHighscore::config() const
 {
- #ifdef HIGHSCORE_DIRECTORY
-	if (!d->mConfig) {
-		//AB: is instanceName() correct? MUST be the same for all
-		//processes of the game!
-		QString file=QString::fromLocal8Bit("%1/%2").arg(HIGHSCORE_DIRECTORY).arg(KGlobal::instance()->instanceName());
                
-		d->mConfig = new KSimpleConfig(file);
-	}
-	return d->mConfig;
- #else
-	return kapp->config();
- #endif
+    return (d->global ? _config : kapp->config());
 }
 
 void KHighscore::writeEntry(int entry, const QString& key, const QVariant& value)
 {
- // save the group in case that we are on kapp->config()
+ Q_ASSERT( isLocked() );
  KConfigGroupSaver cg(config(), group());
  QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -83,5 +175,5 @@ void KHighscore::writeEntry(int entry, c
 void KHighscore::writeEntry(int entry, const QString& key, int value)
 {
- // save the group in case that we are on kapp->config()
+ Q_ASSERT( isLocked() );
  KConfigGroupSaver cg(config(), group());
  QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -91,4 +183,5 @@ void KHighscore::writeEntry(int entry, c
 void KHighscore::writeEntry(int entry, const QString& key, const QString &value)
 {
+ Q_ASSERT (isLocked() );
  KConfigGroupSaver cg(config(), group());
  QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -152,9 +245,8 @@ const QString& KHighscore::highscoreGrou
 QString KHighscore::group() const
 {
- if (highscoreGroup().isNull()) {
-	return GROUP;
- } else {
-	return QString("%1_%2").arg(GROUP).arg(highscoreGroup());
- }
+    if ( highscoreGroup().isNull() )
+        return (d->global ? QString::null : GROUP);
+    return (d->global ? highscoreGroup()
+            : QString("%1_%2").arg(GROUP).arg(highscoreGroup()));
 }
 
@@ -163,5 +255,7 @@ bool KHighscore::hasTable() const
 
 void KHighscore::sync()
-{ config()->sync(); }
+{
+    writeAndUnlock();
+}
 
 #include "khighscore.moc"


["kconfigrawbackend.cpp" (text/x-c++src)]

/*
   This file is part of the KDE games library
   Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include "kconfigrawbackend.h"
#include "kconfigrawbackend.moc"

#include <unistd.h>
#include <qfile.h>


KConfigRawBackEnd::KConfigRawBackEnd(KConfigBase *_config, int fd)
    : KConfigINIBackEnd(_config, QString::null, "config", false),
      _fd(fd), _stream(0)
{
    _file.open(IO_ReadOnly, _fd);
}

KConfigRawBackEnd::~KConfigRawBackEnd()
{
    if (_stream) fclose(_stream);
}

bool KConfigRawBackEnd::parseConfigFiles()
{
    _file.reset();
    parseSingleConfigFile(_file);
    return true;
}

void KConfigRawBackEnd::sync(bool bMerge)
{
  // write-sync is only necessary if there are dirty entries
  if ( !pConfig->isDirty() || pConfig->isReadOnly() ) return;

  _file.reset();
  KEntryMap aTempMap;
  getEntryMap(aTempMap, true, bMerge ? &_file : 0);

  if ( _stream==0 ) {
      _stream = fdopen(_fd, "w");
      if ( _stream==0 ) return;
  }
  ftruncate(_fd, 0);
  writeEntries(_stream, aTempMap);
  fflush(_stream);
}

["kconfigrawbackend.h" (text/x-chdr)]

/*
   This file is part of the KDE games library
   Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#ifndef _KCONFIGRAWBACKEND_H
#define _KCONFIGRAWBACKEND_H

#include <qfile.h>

#include <kconfigbackend.h>
#include <ksimpleconfig.h>


class KConfigRawBackEnd : public KConfigINIBackEnd
{
public:
    KConfigRawBackEnd(KConfigBase *_config, int fd);
    ~KConfigRawBackEnd();

    bool parseConfigFiles();

    void sync(bool bMerge = true);

private:
    int   _fd;
    FILE *_stream;
    QFile _file;

    class KConfigRawBackEndPrivate;
    KConfigRawBackEndPrivate *d;
};

class KRawConfig : public KSimpleConfig
{
    Q_OBJECT
public:
    KRawConfig(int fd, bool readOnly)
        : KSimpleConfig(new KConfigRawBackEnd(this, fd), readOnly) {}
};


#endif

["kfilelock.cpp" (text/x-c++src)]

/*
    This file is part of the KDE games library
    Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include "kfilelock.h"

#include <unistd.h>
#include <sys/file.h>
#include <errno.h>

#include <kdebug.h>


KFileLock::KFileLock(int fd)
    : _fd(fd), _locked(false)
{}

int KFileLock::lock()
{
    kdDebug(11002) << "lock fd=" << _fd << endl;
#ifdef F_SETLK
# ifndef SEEK_SET
#  define SEEK_SET 0
# endif
    struct flock lock_data;
    lock_data.l_type = F_WRLCK;
    lock_data.l_whence = SEEK_SET;
    lock_data.l_start = lock_data.l_len = 0;
    if ( fcntl(_fd, F_SETLK, &lock_data)==-1 ) {
        if ( errno==EAGAIN ) return -2;
        return -1;
    }
#else
# ifdef LOCK_EX
    if ( flock (_fd, LOCK_EX|LOCK_NB)==-1 ) {
        if ( errno==EWOULDBLOCK ) return -2;
        return -1;
    }
# else
    if ( lockf(_fd, F_TLOCK, 0)==-1 ) {
        if ( errno==EACCES ) return -2;
        return -1;
    }
# endif
#endif
    _locked = true;
    return 0;
}

KFileLock::~KFileLock()
{
    unlock();
}

void KFileLock::unlock()
{
    if ( !_locked ) return;
    kdDebug(11002) << "unlock" << endl;
# ifdef F_SETLK
    struct flock lock_data;
    lock_data.l_type = F_UNLCK;
    lock_data.l_whence = SEEK_SET;
    lock_data.l_start = lock_data.l_len = 0;
    (void)fcntl(_fd, F_SETLK, &lock_data);
# else
#  ifdef F_ULOCK
    lockf(_fd, F_ULOCK, 0);
#  else
    flock(_fd, LOCK_UN);
#  endif
# endif
    _locked = false;
}

["kfilelock.h" (text/x-chdr)]

/*
    This file is part of the KDE games library
    Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/
#ifndef KFILELOCK_H
#define KFILELOCK_H


class KFileLock
{
public:
    KFileLock(int fd);

    /** Call unlock(). */
    ~KFileLock();

    /** @return the file descriptor. */
    int fd() const { return _fd; }

    /*
     * Lock the file.
     * @return 0 on success, -1 on failure (no permission) and -2 if another
     * process is currently locking the file.
     */
    int lock();

    /** Unlock the file. */
    void unlock();

    /** @return true if we currently lock the file. */
    bool isLocked() const { return _locked; }

private:
    int   _fd;
    bool  _locked;
};


#endif


_______________________________________________
kde-games-devel mailing list
kde-games-devel@mail.kde.org
http://mail.kde.org/mailman/listinfo/kde-games-devel


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic